Besides redefining console._commandLineAPI
,
there are some other ways to break into InjectedScriptHost on WebKit browsers, to prevent or alter the evaluation of expressions entered into the developer's console.
Edit:
Chrome has fixed this in a past release. - which must have been before February 2015, as I created the gist at that time
So here's another possibility. This time we hook in, a level above, directly into InjectedScript
rather than InjectedScriptHost
as opposed to the prior version.
Which is kind of nice, as you can directly monkey patch InjectedScript._evaluateAndWrap
instead of having to rely on InjectedScriptHost.evaluate
as that gives you more fine-grained control over what should happen.
Another pretty interesting thing is, that we can intercept the internal result when an expression is evaluated and return that to the user instead of the normal behavior.
Here is the code, that does exactly that, return the internal result when a user evaluates something in the console.
var is;
Object.defineProperty(Object.prototype,"_lastResult",{
get:function(){
return this._lR;
},
set:function(v){
if (typeof this._commandLineAPIImpl=="object") is=this;
this._lR=v;
}
});
setTimeout(function(){
var ev=is._evaluateAndWrap;
is._evaluateAndWrap=function(){
var res=ev.apply(is,arguments);
console.log();
if (arguments[2]==="completion") {
//This is the path you end up when a user types in the console and autocompletion get's evaluated
//Chrome expects a wrapped result to be returned from evaluateAndWrap.
//You can use `ev` to generate an object yourself.
//In case of the autocompletion chrome exptects an wrapped object with the properties that can be autocompleted. e.g.;
//{iGetAutoCompleted: true}
//You would then go and return that object wrapped, like
//return ev.call (is, '', '({test:true})', 'completion', true, false, true);
//Would make `test` pop up for every autocompletion.
//Note that syntax as well as every Object.prototype property get's added to that list later,
//so you won't be able to exclude things like `while` from the autocompletion list,
//unless you wou'd find a way to rewrite the getCompletions function.
//
return res; //Return the autocompletion result. If you want to break that, return nothing or an empty object
} else {
//This is the path where you end up when a user actually presses enter to evaluate an expression.
//In order to return anything as normal evaluation output, you have to return a wrapped object.
//In this case, we want to return the generated remote object.
//Since this is already a wrapped object it would be converted if we directly return it. Hence,
//`return result` would actually replicate the very normal behaviour as the result is converted.
//to output what's actually in the remote object, we have to stringify it and `evaluateAndWrap` that object again.`
//This is quite interesting;
return ev.call (is, null, '(' + JSON.stringify (res) + ')', "console", true, false, true)
}
};
},0);
It's a bit verbose, but I thought I put some comments into it
So normally, if a user, for example, evaluates [1,2,3,4]
you'd expect the following output:
After monkeypatching InjectedScript._evaluateAndWrap
evaluating the very same expression, gives the following output:
As you see the little-left arrow, indicating output, is still there, but this time we get an object. Where the result of the expression, the array [1,2,3,4]
is represented as an object with all its properties described.
I recommend trying to evaluate this and that expression, including those that generate errors. It's quite interesting.
Additionally, take a look at the is
- InjectedScriptHost
- object. It provides some methods to play with and get a bit of insight into the internals of the inspector.
Of course, you could intercept all that information and still return the original result to the user.
Just replace the return statement in the else path by a console.log (res)
following a return res
. Then you'd end up with the following.
End of Edit
This is the prior version which was fixed by Google. Hence not a possible way anymore.
One of it is hooking into Function.prototype.call
Chrome evaluates the entered expression by call
ing its eval function with InjectedScriptHost
as thisArg
var result = evalFunction.call(object, expression);
Given this, you can listen for the thisArg
of call
being evaluate
and get a reference to the first argument (InjectedScriptHost
)
if (window.URL) {
var ish, _call = Function.prototype.call;
Function.prototype.call = function () { //Could be wrapped in a setter for _commandLineAPI, to redefine only when the user started typing.
if (arguments.length > 0 && this.name === "evaluate" && arguments [0].constructor.name === "InjectedScriptHost") { //If thisArg is the evaluate function and the arg0 is the ISH
ish = arguments[0];
ish.evaluate = function (e) { //Redefine the evaluation behaviour
throw new Error ('Rejected evaluation of: \n\'' + e.split ('\n').slice(1,-1).join ("\n") + '\'');
};
Function.prototype.call = _call; //Reset the Function.prototype.call
return _call.apply(this, arguments);
}
};
}
You could e.g. throw an error, that the evaluation was rejected.
Here is an example where the entered expression gets passed to a CoffeeScript compiler before passing it to the evaluate
function.