[GreaseMonkey]如何hook已有函数
GreaseMonkey这个插件大家都早已熟悉了。最近我遇到一个问题:需要让页面在调用完某个函数之后自动执行我的函数。 其实这个并不难,写个函数替换原有的函数即可:
function hook() {
var f = unsafeWindow.foo; // 保存旧函数
unsafeWindow.foo = function() { // 定义新函数
alert("Hello!"); // 先执行我们的处理
f(); // 再执行旧函数
}
}
然后加载到页面上:
setTimeout(hook, 1000);
这样,页面再执行foo函数时,就会先执行我们的alert("Hello!")
了。
不过这个函数还有很大的问题。比如,原有函数的参数不能正确传给foo,返回值无法取出来,无法应用到对象中的方法,通用性不好等。 下面来一个个解决。
返回值的问题比较好办,让它return一下就行了:
function hook() {
var f = unsafeWindow.foo; // 保存旧函数
unsafeWindow.foo = function() { // 定义新函数
alert("Hello!"); // 先执行我们的处理
return f(); // 再执行旧函数
}
对象方法的意思是,如果要hook的函数不是全局函数,而是某个对象的方法的话,上述函数就无法正确执行。
var object = {
value: 10,
foo: function () { alert("Foo!" + this.value); }
};
object.foo();
此时如果只是简单修改一下最初的GreaseMonkey脚本,改成这样的话:
// 注意这种实现方法是错误的!
function hook() {
var f = unsafeWindow.object.foo; // 保存旧函数
unsafeWindow.object.foo = function() { // 定义新函数
alert("Hello!"); // 先执行我们的处理
return f(); // 再执行旧函数
}
}
那么执行foo()
时你会发现,本应输出”Foo!10
“,但实际输出结果变成了”Foo!undefined
“。
这是因为临时保存下来的 f 函数在执行时没有绑定到unsafeWindow.object
上,因此找不到this.value
。
解决方法是在调用 f 时使用 apply:
// 正确的方法
function hook() {
var f = unsafeWindow.object.foo; // 保存旧函数
unsafeWindow.object.foo = function() { // 定义新函数
alert("Hello!"); // 先执行我们的处理
return f.apply(unsafeWindow.object); // 再执行旧函数
}
}
有了apply,参数问题也迎刃而解:
function hook() {
var f = unsafeWindow.object.foo; // 保存旧函数
unsafeWindow.object.foo = function() { // 定义新函数
alert("Hello!"); // 先执行我们的处理
return f.apply(unsafeWindow.object, arguments); // 再执行旧函数
}
}
最后的问题就是这个函数通用性还不好。代码中把要hook的函数(unsafeWindow.object.foo
)写死了,
这样每hook一个函数都要写专用的函数才行。下面试着让它通用一些:
/**
* hook系统中的已有函数
* @param object {Object} 要hook的函数所在的对象
* @param name {String} 要hook的函数名称
* @param pre {Function} 原函数执行前要执行的动作
* @param post {Function} 原函数执行后要执行的动作
* @return 旧函数的返回值
*/
function hook(object, func, pre, post) {
var f = object[func]; // 保存旧函数
object[func] = function() { // 定义新函数
if (pre) pre.apply(window, arguments); // 旧函数执行前执行的自定义函数
var ret = f.apply(object, arguments); // 执行旧函数
if (post) post.apply(window, arguments); // 旧函数执行后执行的自定义函数
return ret;
}
}
这样这个函数就相当通用了。在hook时需要执行以下代码:
setTimeout(function() {
hook(unsafeWindow.object,
"foo",
function(){alert("pre")},
function(){alert("post")}
);
}, 1000);
当然上述函数还有可以改进的地方。如调用pre和post时直接apply到了window上, 这样如果在hook时给出的pre和post不是全局函数而是某个对象的方法的话, 函数就无法正常执行了。不过对于一般的应用来说,上面的方法已经足够了吧。