How to create a JavaScript interpreter in JavaScript using eval? - javascript

It should be simple to create JavaScript intepreter in JavaScript using eval. I got this (using jQuery terminal):
term = $('#term_demo').terminal(function(command, term) {
if (command !== '') {
var result = window.eval("(" + command + ")");
if (result !== undefined) {
term.echo(String(result));
}
} else {
term.echo('');
}
}, {
greetings: 'Javascript Interpreter',
name: 'js_demo',
height: 200,
prompt: 'js> '
});
Demo
but it don't work when I execute function foo() { ... } the foo is not defined I need to use foo = function() { ... }. eval act like executed within (function() { <code> })(). Can it be there more complicated code that will not work too?
Is it possible to create a JavaScript interpreter using simple code without use the of js.js, that will work the same as browser console?

I've created a bookmarklet which appends a kind of REPL in a page, designed for the major five browsers (Chrome 1+, IE 6+, Firefox 1+, Safari 3+, Opera 9+Can't remember the exacte version).
The core component, which evaluates the code is posted below, slightly modified + annotated.
/**
* Evaluates some code in the global scope.
* #param String code: Code to eval
* #return Stringified result, prefixed with 'E:' if error.
*/
function globalEval(/*string*/ code) {
var win = window, r, raw;
try {
if (win.execScript) { // eval in IE sucks, so use execScript instead
r = win.$_$_$globalEval$_$_$ = {c:code};
win.execScript('try{$_$_$globalEval$_$_$.r=eval($_$_$globalEval$_$_$.c);}catch(e){$_$_$globalEval$_$_$.e=e}');
// /*Optional clean-up:*/ delete win.$_$_$globalEval$_$_$;
if (r.e) throw r.e; // Error
raw = r.r;
} else {
raw = win.eval(code);
}
r = '' + raw; // Stringify in the try-block
// It is possible that an error is thrown
// for example, for the following code: ({toString:1})
} catch(err) {
r = (err + ''); // Convert error to string
// IE: If found, "[object" will be at index zero, which is falsy
if (!r.indexOf('[object')) r = err.message;
// r =
r = 'E:' + (raw=r);
} finally {
// raw = unmodified result (or Error instance)
// FOR THIS EXAMPLE, raw is not used, and string r is returned
return /*string*/ r;
}
}
I've implemented the functionality in a form, which contains several controls including an input+output textarea.
Note: The code is evaluated in the global context. And such, any variables in code will be leaked to the global scope. For an interpreter, you could use an iframe to create a new scope (and modify var win in my function).
var win = frames['name_of_frame'], ... // or
var win = frame_DOM_element.contentWindow, ...

The parenthesis you're appending around your incoming command produces illegal syntax. You should wrap it with an anonymous self-executing function instead.
Example: http://jsfiddle.net/bW6Fv/1/
var command = "function foo(x){return x+x;} alert(foo(10));";
window.eval("!function(){" + command + "}()");​
EDIT
If you want your evaluated scripts to be available globally, you'll have to leave them unwrapped or explicitly assign values to the global window object.
Example: http://jsfiddle.net/bW6Fv/2/
var command = "window.foo = function(x){return x+x;}; alert(foo(10));";
window.eval("!function(){ " + command + "}();");
window.eval("alert(foo(20));");
command = "function bar(x){return x / 2;}; alert(bar(10));";
window.eval(command);
window.eval("alert(bar(20));");

Related

JavaScript works in console but not in my browser extension [duplicate]

I am developing an firefox extension using webExtensions that would help me ease my work with the scenario below.
I have to click around 50-60 buttons on the site that update the task status. On click of this button, the web page is calling the webpage's updTask(id) JavaScript function that is then making a web-service call to update the task.
I am not able to do this from my content script using the code below:
manifest.json:
"permissions": [
"activeTab",
"cross-domain-content": ["http://workdomain.com/","http://workdomain.org/","http://www.workdomain.com/","http://www.workdomain.org/"]
]
Content-Script code:
function taskUpdate(request, sender, sendResponse) {
console.log(request.start + 'inside task update');
updateTask(45878);
chrome.runtime.onMessage.removeListener(taskUpdate);
}
function updateTask(id) {
//TODO: code to get all buttons and task id's
updTask(id); // Not working
}
Plugin Script:
document.addEventListener("click", function(e) {
if (e.target.classList.contains("startButton")) {
chrome.tabs.executeScript(null, {
file: "/content_scripts/taskUpdate.js"
});
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {start: "start"});
});
return;
}
else if (e.target.classList.contains("clear")) {
chrome.tabs.reload();
window.close();
return;
}
});
Could someone point me in the right direction, what am I missing here??
Your content script is in a different context/scope from that of page scripts (scripts that already exist in the webpage). Your content script has higher privileges than are granted to page scripts. Keeping content scripts separate from page scripts is a normal architecture for browser extensions, which is done for security reasons.
Because your content script is in a different context from page scripts, you can't directly access functions and variables that are defined in page scripts from your content script. There are a few different ways which you can access information in the page context. The cross-browser method of doing so is to cause some specific parts of your code to execute in the page context. I find the most convenient, and cross-browser compatible, way to do so is to create and insert a <script> element into the page's DOM containing the code you want to execute.
You could do something like:
function updateTask(id) {
let newScript = document.createElement('script');
newScript.innerHTML='updTask(' + id + ');';
document.head.appendChild(newScript);
//newScript.remove(); //Can be removed, if desired.
}
The added script gets run in the page context because it is now a <script> element in the DOM. The browser recognizes that a <script> element was added and evaluates it (executes the code contained) as soon as the script which inserted it is no longer processing. It does basically the same thing for any other element you add to the DOM. Because it is part of the page, the code inside the gets run in the page script context/scope.
Getting data back to your content script
There are a variety of ways to communicate between code you have running in the page context and your code in the content script context. My preferred method of doing so is to use CustomEvents. I describe why in the first part of this answer. Generally, I use at least one custom event type to communicate from the page context to the content script context and another from the content script context to the page context. You can use as many CustomEvent types as you want. I'll often use multiple events, each communicating a different thing, rather than a single event type out of which I parse several different types of messages.
Generalized code to execute in the page context from a content script
The easiest way to maintain code which you are going to execute in the page context is to write it as a function in your content script, then inject that function into the page context. Here is some generalized code which will do that while passing parameters to the function you are executing in the page context:
This utility function, executeInPage(), will execute a function in the page context and pass any provided arguments to the function. Arguments must be Object, Array, function, RegExp, Date, and/or other primitives (Boolean, null, undefined, Number, String, but not Symbol).
/* executeInPage takes a function defined in this context, converts it to a string
* and inserts it into the page context inside a <script>. It is placed in an IIFE and
* passed all of the additional parameters passed to executeInPage.
* Parameters:
* func The function which you desire to execute in the page.
* leaveInPage If this does not evaluate to a truthy value, then the <script> is
* immediately removed from the page after insertion. Immediately
* removing the script can normally be done. In some corner cases,
* it's desirable for the script to remain in the page. However,
* even for asynchronous functionality it's usually not necessary, as
* the context containing the code will be kept with any references
* (e.g. the reference to a callback function).
* id If this is a non-blank string, it is used as the ID for the <script>
* All additional parameters are passed to the function executing in the page.
* This is done by converting them to JavaScript code-text and back.
* All such parameters must be Object, Array, functions, RegExp,
* Date, and/or other primitives (Boolean, null, undefined, Number,
* String, but not Symbol). Circular references are not supported.
* If you need to communicate DOM elements, you will need to
* pass selectors, or other descriptors of them (e.g. temporarily
* assign them a unique class), or otherwise communicate them to the
* script (e.g. you could dispatch a custom event once the script is
* inserted into the page context).
*/
function executeInPage(functionToRunInPage, leaveInPage, id) {
//Execute a function in the page context.
// Any additional arguments passed to this function are passed into the page to the
// functionToRunInPage.
// Such arguments must be JSON-ifiable (also Date, Function, and RegExp) (prototypes
// are not copied).
// Using () => doesn't set arguments, so can't use it to define this function.
// This has to be done without jQuery, as jQuery creates the script
// within this context, not the page context, which results in
// permission denied to run the function.
function convertToText(args) {
//This uses the fact that the arguments are converted to text which is
// interpreted within a <script>. That means we can create other types of
// objects by recreating their normal JavaScript representation.
// It's actually easier to do this without JSON.strigify() for the whole
// Object/Array.
var asText = '';
var level = 0;
function lineSeparator(adj, isntLast) {
level += adj - ((typeof isntLast === 'undefined' || isntLast) ? 0 : 1);
asText += (isntLast ? ',' : '') +'\n'+ (new Array(level * 2 + 1)).join('');
}
function recurseObject(obj) {
if (Array.isArray(obj)) {
asText += '[';
lineSeparator(1);
obj.forEach(function(value, index, array) {
recurseObject(value);
lineSeparator(0, index !== array.length - 1);
});
asText += ']';
} else if (obj === null) {
asText +='null';
//undefined
} else if (obj === void(0)) {
asText +='void(0)';
//Special cases for Number
} else if (Number.isNaN(obj)) {
asText +='Number.NaN';
} else if (obj === 1/0) {
asText +='1/0';
} else if (obj === 1/-0) {
asText +='1/-0';
//function
} else if (obj instanceof RegExp || typeof obj === 'function') {
asText += obj.toString();
} else if (obj instanceof Date) {
asText += 'new Date("' + obj.toJSON() + '")';
} else if (typeof obj === 'object') {
asText += '{';
lineSeparator(1);
Object.keys(obj).forEach(function(prop, index, array) {
asText += JSON.stringify(prop) + ': ';
recurseObject(obj[prop]);
lineSeparator(0, index !== array.length - 1);
});
asText += '}';
} else if (['boolean', 'number', 'string'].indexOf(typeof obj) > -1) {
asText += JSON.stringify(obj);
} else {
console.log('Didn\'t handle: typeof obj:', typeof obj, ':: obj:', obj);
}
}
recurseObject(args);
return asText;
}
var newScript = document.createElement('script');
if(typeof id === 'string' && id) {
newScript.id = id;
}
var args = [];
//using .slice(), or other Array methods, on arguments prevents optimization
for(var index=3;index<arguments.length;index++){
args.push(arguments[index]);
}
newScript.textContent = '(' + functionToRunInPage.toString() + ').apply(null,'
+ convertToText(args) + ");";
(document.head || document.documentElement).appendChild(newScript);
if(!leaveInPage) {
//Synchronous scripts are executed immediately and can be immediately removed.
//Scripts with asynchronous functionality of any type must remain in the page
// until complete.
document.head.removeChild(newScript);
}
return newScript;
};
Using excuteInPage():
function logInPageContext(arg0,arg1,arg2,arg3){
console.log('arg0:', arg0);
console.log('arg1:', arg1);
console.log('arg2:', arg2);
console.log('arg3:', arg3);
}
executeInPage(logInPageContext, false, '', 'This', 'is', 'a', 'test');
/* executeInPage takes a function defined in this context, converts it to a string
* and inserts it into the page context inside a <script>. It is placed in an IIFE and
* passed all of the additional parameters passed to executeInPage.
* Parameters:
* func The function which you desire to execute in the page.
* leaveInPage If this does not evaluate to a truthy value, then the <script> is
* immediately removed from the page after insertion. Immediately
* removing the script can normally be done. In some corner cases,
* it's desirable for the script to remain in the page. However,
* even for asynchronous functionality it's usually not necessary, as
* the context containing the code will be kept with any references
* (e.g. the reference to a callback function).
* id If this is a non-blank string, it is used as the ID for the <script>
* All additional parameters are passed to the function executing in the page.
* This is done by converting them to JavaScript code-text and back.
* All such parameters must be Object, Array, functions, RegExp,
* Date, and/or other primitives (Boolean, null, undefined, Number,
* String, but not Symbol). Circular references are not supported.
* If you need to communicate DOM elements, you will need to
* pass selectors, or other descriptors of them (e.g. temporarily
* assign them a unique class), or otherwise communicate them to the
* script (e.g. you could dispatch a custom event once the script is
* inserted into the page context).
*/
function executeInPage(functionToRunInPage, leaveInPage, id) {
//Execute a function in the page context.
// Any additional arguments passed to this function are passed into the page to the
// functionToRunInPage.
// Such arguments must be JSON-ifiable (also Date, Function, and RegExp) (prototypes
// are not copied).
// Using () => doesn't set arguments, so can't use it to define this function.
// This has to be done without jQuery, as jQuery creates the script
// within this context, not the page context, which results in
// permission denied to run the function.
function convertToText(args) {
//This uses the fact that the arguments are converted to text which is
// interpreted within a <script>. That means we can create other types of
// objects by recreating their normal JavaScript representation.
// It's actually easier to do this without JSON.strigify() for the whole
// Object/Array.
var asText = '';
var level = 0;
function lineSeparator(adj, isntLast) {
level += adj - ((typeof isntLast === 'undefined' || isntLast) ? 0 : 1);
asText += (isntLast ? ',' : '') +'\n'+ (new Array(level * 2 + 1)).join('');
}
function recurseObject(obj) {
if (Array.isArray(obj)) {
asText += '[';
lineSeparator(1);
obj.forEach(function(value, index, array) {
recurseObject(value);
lineSeparator(0, index !== array.length - 1);
});
asText += ']';
} else if (obj === null) {
asText +='null';
//undefined
} else if (obj === void(0)) {
asText +='void(0)';
//Special cases for Number
} else if (Number.isNaN(obj)) {
asText +='Number.NaN';
} else if (obj === 1/0) {
asText +='1/0';
} else if (obj === 1/-0) {
asText +='1/-0';
//function
} else if (obj instanceof RegExp || typeof obj === 'function') {
asText += obj.toString();
} else if (obj instanceof Date) {
asText += 'new Date("' + obj.toJSON() + '")';
} else if (typeof obj === 'object') {
asText += '{';
lineSeparator(1);
Object.keys(obj).forEach(function(prop, index, array) {
asText += JSON.stringify(prop) + ': ';
recurseObject(obj[prop]);
lineSeparator(0, index !== array.length - 1);
});
asText += '}';
} else if (['boolean', 'number', 'string'].indexOf(typeof obj) > -1) {
asText += JSON.stringify(obj);
} else {
console.log('Didn\'t handle: typeof obj:', typeof obj, ':: obj:', obj);
}
}
recurseObject(args);
return asText;
}
var newScript = document.createElement('script');
if(typeof id === 'string' && id) {
newScript.id = id;
}
var args = [];
//using .slice(), or other Array methods, on arguments prevents optimization
for(var index=3;index<arguments.length;index++){
args.push(arguments[index]);
}
newScript.textContent = '(' + functionToRunInPage.toString() + ').apply(null,'
+ convertToText(args) + ");";
(document.head || document.documentElement).appendChild(newScript);
if(!leaveInPage) {
//Synchronous scripts are executed immediately and can be immediately removed.
//Scripts with asynchronous functionality of any type must remain in the page
// until complete.
document.head.removeChild(newScript);
}
return newScript;
};
The text for this answer was largely taken from my other answers: this one and this one.
I had a similar issue today where my webextension had to call a javascript function that can only be accessed from the context of a specific web page and I was kinda annoyed that I was supposed to do all the script injection and json serialization and whatnot. But there's actually a pretty simple solution:
window.eval(`updTask(${id})`)
eval will execute whatever code you pass to it in the context of the page instead of your content script.
yeah, I know eval is evil, but in this context it really makes sense, because what I want to do (execute arbitrary code in the context of a web page) is evil by definition. It's like parsing HTML with regex; once you're at that point, you're tainted. Also, you're finally free from the burdens of writing code that is considered lawful good. So let's embrace the eval, just this one time ;)

V8Engine.Execute returns 'undefined', whereas Microsoft.JScript's eval returns a value

I am looking to replace the Microsoft.JScript engine in our C#.NET 4.0 program with a V8.Net engine in order to be able to execute passed in javascript functions. The program runs fine and correctly executes a number of scripts until it hits the following;
const t_sch_ohd = 0.1;
const t_fn = 8.302;
const t_byte = 0.04307;
var n_byte = Lookup.IndexedMsgSize (1);
var t_total = t_fn + (n_byte * t_byte) + t_sch_ohd;
The V8Engine.Execute command returns undefined, whereas our Microsoft.JScript implementation returns 9.091119999999998 which I'm not sure is correct either.
I get the feeling this is something to do with how each engine handles what is considered an 'output' at the end of executing the script but I'm not sure.
Lookup.IndexesMsgSize is a function we have defined in our C#.NET application, and is consistent through both versions, I believe it returns 16 in this instance;
public uint IndexedMsgSize(uint mIndex)
{
// only intended for MsgProcess_Fn
if (m_message == null)
{
if (m_function == null)
{
throw new RaceException("Function reference not specified for Lookup.IndexedMsgSize");
}
uint count = (uint)m_function.RelatedMessagesByUDC[((int)mIndex) - 1].MessageLength;
return count;
}
else
{
throw new RaceException("message reference not allowed for Lookup.IndexedMsgSize");
}
}
The implementation for the Microsoft.JScript evaluation is as follows;
public string Execute(string script)
{
string result = "";
try
{
// EVALUATE THE SCRIPT
result = Evaluator.Eval(script);
//using (var engine = new ScriptEngine("jscript"))
//{
// ParsedScript parsed =
// engine.Parse("function MyFunc(x){return eval(x,'unsafe')}");
// engine.SetNamedItem("Lookup", m_lookup);
// result = parsed.CallMethod("MyFunc", script).ToString();
//}
}
catch (Exception ex)
{
// JScript error?
if (ex.Source.CompareTo("Microsoft.JScript") == 0)
{
// Yes, turn it into a Race JScript Exception to avoid a stack trace dump.
throw new RaceScriptException(ex as JScriptException);
}
// Not a JScript error, so just re-throw exception.
throw ex;
}
return result;
}
Evaluator is a class defined through a .dll, the Eval function is as follows in Javascript;
static function Eval(expression : String) : String {
return eval(expression,'unsafe');
}
And In our V8.Net Implementation we simply use the built in V8Engine.Execute command;
public string Execute(string script)
{
string result = "";
try
{
// EVALUATE THE SCRIPT
//Create the V8.Net Engine
V8Engine e = new V8Engine();
//Set the Lookup instance as a global object so that the JS code in the V8.Net wrapper can access it
e.GlobalObject.SetProperty("Lookup", m_lookup, null, true, ScriptMemberSecurity.Permanent);
//Execute the script
result = e.Execute(script);
//Deprecated code:
//result = Evaluator.Eval(script);
//using (var engine = new ScriptEngine("jscript"))
//{
// ParsedScript parsed =
// engine.Parse("function MyFunc(x){return eval(x,'unsafe')}");
// engine.SetNamedItem("Lookup", m_lookup);
// result = parsed.CallMethod("MyFunc", script).ToString();
//}
}
catch (Exception ex)
{
// V8.NET error?
if (ex.Source.CompareTo("V8.Net") == 0)
{
// Yes, turn it into a Race V8 Exception to avoid a stack trace dump.
throw (V8Exception)ex;
}
// Not a V8 error, so just re-throw exception.
throw ex;
}
return result;
}
I hope someone can shed some light on why the above results are happening,
Thanks in advance!
JS engine uses eval function when you pass script to it. In turn,
undefined is correct result of your script execution, because
eval() returns the value of the last expression evaluated
MDN eval docs
In your code:
var t_total = t_fn + (n_byte * t_byte) + t_sch_ohd;
Variable Statement is the last executed expression. And it always returns undefined.
A variable statement declares variables that are created as defined in 10.5. Variables are initialised to undefined when created. A variable with an Initialiser is assigned the value of its AssignmentExpression when the VariableStatement is executed, not when the variable is created.
In your case t_fn + (n_byte * t_byte) + t_sch_ohd; is AssignmentExpression initialiser. VariableStatement -> AssignmentExpression;
So, your code folows:
Perform AssignmentExpression
Perform VariableStatement
There couple of ways to overcome your problem:
Implicitly define variable:
t_total = t_fn + (n_byte * t_byte) + t_sch_ohd;
Return the result explicitly by:
var t_total = t_fn + (n_byte * t_byte) + t_sch_ohd;
t_total;
Define variable explicitly, but above:
var t_total;
t_total = t_fn + (n_byte * t_byte) + t_sch_ohd;
Microsoft.JScript works as you expects just due to misbehaviour.
For deep understanding of JS nature look through http://es5.github.io/

Release local variable to a window scoped variable?

I have a global variable NS which I can access from the console as such:
NS.some_func();
NS is populated using a method called extendSafe()
some_scope.extendSafe = function (o1, o2) {
var key;
for (key in o2) {
if (o2.hasOwnProperty(key) && o1.hasOwnProperty(key)) {
throw "naming collision: " + key;
}
o1[key] = o2[key];
}
return o1;
};
This is used by setting up a public scope called $P and then copying over to the global scope NS once all the $P methods have been defined.
I want to to it this way so I can verify that I'm not writing over any properties.
This worked well until I tried to save a local variable to $P for later copying to NS. Because the interpreter does not know that $P will be "released" to the window scope, it does not know to keep the local variable active. So I can not use my safeExtend method.
I verified this was the issue by doing a direct copy as such:
NS.local = local;
I can now access NS.local from the console.
However if I copy it over as I wish to do:
$P.local = local;
extendSafe(NS, $P);
The local variable is not available.
How can I safely release it, i.e. using safeExtend()?
Code Snippet
Issue is commented as
// hacked needs a fix
$P.machine = function (obj) {
var pipe,
data_send,
ajax_type,
wait_animation,
set;
wait_animation = document.getElementById('wait_animation');
set = false;
pipe = NS.makePipe(obj);
if ($R.Parsel[pipe.model] === undefined) {
return;
}
time('start');
if ($R.Parsel[pipe.model].hasOwnProperty("pre")) {
pipe = $R.Parsel[pipe.model].pre(pipe);
} else {
return;
}
if (pipe.form_data) {
ajax_type = 'multi';
var form_data = pipe.form_data;
delete pipe.form_data;
form_data.append("pipe", JSON.stringify(pipe));
data_send = form_data;
} else {
ajax_type = 'post';
data_send = 'pipe=' + encodeURIComponent(JSON.stringify(pipe));
}
if (pipe.state === true) {
time('middle');
if (wait_animation) {
set = true;
wait_animation.style.opacity = 1;
}
NS.ajax({
type: ajax_type,
url: NS.Reg.get('path') + NS.Reg.get('path_ajax'),
data: data_send,
callback: function (pipe_string_receive) {
var pass_prefix = pipe_string_receive.slice(0, 3),
times;
if (wait_animation && set) {
wait_animation.style.opacity = 0;
}
if (pass_prefix === '|D|') {
NS.log('|DEBUG| ' + pipe_string_receive.slice(3));
} else if (pass_prefix === '|A|') {
time('middle');
pipe = JSON.parse(pipe_string_receive.slice(3));
if ($R.Parsel[pipe.model].hasOwnProperty("post")) {
pipe = $R.Parsel[pipe.model].post(pipe);
times = time('finish');
pipe.time.pre = times[0];
pipe.time.transit = times[1];
pipe.time.post = times[2];
// works but hacked needs a fix
NS.last = pipe;
// will not exendSafe()
$P.last = pipe;
} else {
return;
}
} else {
throw "<No 'A' or 'D'>" + pipe_string_receive;
}
}
});
}
};
I see you've solved the problem, but I have a feeling that there's something you're misunderstanding about JavaScript:
This worked well until I tried to save a local variable to $P for later copying to NS. Because the interpreter does not know that $P will be "released" to the window scope, it does not know to keep the local variable active. So I can not use my safeExtend method.
I verified this was the issue by doing a direct copy as such:
NS.local = local;
I can now access NS.local from the console.
However if I copy it over as I wish to do:
$P.local = local;
extendSafe(NS, $P);
The local variable is not available.
How can I safely release it, i.e. using safeExtend()?
This doesn't make sense. JavaScript is very good at keeping track of references to objects. If there are any references to an object, it won't garbage collect the object. I have no idea what it could mean to "release an object to the window scope". There isn't really any such concept, just objects and references to them.
I tried looking through your original code, but there's a lot of code there that isn't related to the problem. If you were to simplify it to a minimal test case, I'll bet a simpler solution would become evident.
I do see one issue in your smaller snippet above. You defined your extendSafe() function as some_scope.extendSafe(), but here you're calling it with a plain extendSafe() call and no reference to some_scope. Did it actually call the function? Is this just a typo in the smaller example?
Of course, if you're just happy to have found a solution and want to move on, that's quite understandable! I just have a strong feeling that there's extra code here that you don't need.

Assertions in JavaScript

Extensively reading about various assertion frameworks in JavaScript. Is there any kind of de-facto/most common "standard" library/framework? When selecting one - which points are most worth noticing?
The (only) requirement I can think about is close-to-zero performance overhead when in production mode.
Two possible solutions:
Have your build release script remove the Assert lines.
or
Have your build script override the Assert function so it is just an empty function. Downside to this is if you assert call has logic in it [aka assert( x > 100 , "foo" )] than that logic [x > 100] is still going to be run.
Here is what I use:
When I'm working on the code I have initDevMode(); at the top of the file I'm working with, and when I'm ready to release to production, I just remove that line and all the asserts just go to an empty function.
/**
* Log a message to console:
* either use jquery's console.error
* or a thrown exception.
*
* call initDevMode(); before use to activate
* use with:
* assert(<condition>, "message");
* eg: assert(1 != 1, "uh oh!");
*
* Log errors with:
* errorLog(message);
* eg: errorLog(xhr.status);
*/
assert = function(test, msg) { }
errorLog =function(msg) { }
initDevMode = function() {
assert = function(test, msg) {
msg = msg || "(no error message)";
if(!test) {
try {
throw Error();
} catch(e) {
var foo = e;
var lines = e.stack.split('\n');
for(i in lines) {
if(i > 2) {
errorLog(msg + lines[i]);
}
}
}
}
throw("Assertion failed with: " + msg);
};
errorLog = function(msg) {
if(typeof console.error == 'function') {
console.error(msg);
} else {
function errorLog(msg) {
console.log("foo");
setTimeout(function() {
throw new Error(msg);
}, 0);
}
}
};
}
I use the following to replace console.assert when it's unavailable for whatever reason.
It's definitely not a de-facto standard, and it is far from ideal, but it does satisfy your requirement that the assertion not be evaluated in production mode. Also, it shows you the expression that triggered the failed assertion, which aids debugging.
The screwy calling syntax (with a function expression) is there to create a closure, so that the assert function has access to the same variables that its caller had access to.
I suspect that this has high compile-time and run-time overhead, but I haven't attempted to verify that.
function assert(func) {
var name;
if (typeof(ENABLE_ASSERTIONS) !== "undefined" && !ENABLE_ASSERTIONS) {
return;
}
name = arguments.callee.caller;
name = name ? name.name : "(toplevel)";
if (!func()) {
throw name + ": assertion failed: " + ('' + func).replace(/function[^(]*\([^)]*\)[^{]*{[^r]*return/, '').replace(/;[ \t\n]*}[ \t\n]*$/, '');
}
}
Using it looks like:
function testAssertSuccess() {
var i = 1;
assert(function() { return i === 1; });
}
function testAssertFailure() {
var j = 1;
assert(function() { return j === 2; });
}
ENABLE_ASSERTIONS = true;
testAssertSuccess();
testAssertFailure();
HTH!
Take a look to Jascree; basically it is a tool that can remove assertions with almost arbitrary logic from your code. It is handy to use as a batch processor to generate your production code or for a fastcgi-backed scripts directory that you can use when you need to test performance/profile your code.

Find and execute Javascript fragments in a bunch of HTML

I need to detect and eval the Javascript code contained in a string.
The following code works, but it only evaluates the first <script>...</script> it founds.
function executeJs(html) {
var scriptFragment = "<script(.+?)>(.+?)<\/script>";
match = new RegExp(scriptFragment, "im");
var matches = html.match(match);
if (matches.length >= 2) {
eval(matches[2]);
}
}
I wonder if there is a method that allows me to iterate and execute all Javascript fragments.
The reason it only takes the first one is because you're missing the g flag. Try this:
function executeJs(html) {
var scriptFragment = '<script(.*?)>(.+?)<\/script>';
var re = new RegExp(scriptFragment, 'gim'), match;
while ((match = re.exec(html)) != null) {
eval(match[2]);
}
}
executeJs('<script>alert("hello")</script>abc<script>alert("world")</script>');
Here is some code that does the same thing in a slightly different way. You can pass the string to the function and it will eval all the script tags and return the cleaned source(without script). There is also a slight difference in the way IE handles it, that is handled in the code as well, you may adapt it to your requirements. Also, the evaluated code has the global context. Hope it helps.
function parseScript(_source)
{
var source = _source;
var scripts = new Array();
// Strip out tags
while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1)
{
var s = source.indexOf("<script");
var s_e = source.indexOf(">", s);
var e = source.indexOf("</script", s);
var e_e = source.indexOf(">", e);
// Add to scripts array
scripts.push(source.substring(s_e+1, e));
// Strip from source
source = source.substring(0, s) + source.substring(e_e+1);
}
// Loop through every script collected and eval it
for(var i=0; i<scripts.length; i++)
{
try
{
//eval(scripts[i]);
if(window.execScript)
{
window.execScript(scripts[i]); // IE
}
else
{
window.setTimeout(scripts[i],0); // Changed this from eval() to setTimeout() to get it in Global scope
}
}
catch(ex)
{
// do what you want here when a script fails
alert("Javascript Handler failed interpretation. Even I am wondering why(?)");
}
}
// Return the cleaned source
return source;
}
Blixt should be right...
You may also take a look at prototype's String.evalScripts function.
http://api.prototypejs.org/language/string.html#evalscripts-instance_method

Categories