Greasemonkey 最初只是 Firefox 的一个扩展,不过流行之后,很快被其它的浏览器以不同形式采纳。IE 我不喜欢,此文就不关心它了。剩下的市场份额较高的是 Chrome 和 Firefox, 本文就简单讨论一下如何写出同时支持这两种浏览器的 user script (所谓“跨浏览器”).
最早的时候也是热心用户给 Chrome 写了插件让它支持 user scripts, 但是现在 Chrome 不再另外需要插件就可以支持了。它的处理方式是每次安装 user script 的时候,自动把它转换成一个扩展!刚开始在 Chrome 上调试脚本的时候,我还尝试在磁盘上找到它存储脚本的目录——在 Firefox 里我都是直接编辑脚本,保存,刷新页面马上看到效果,简单粗暴。肯定有更简单的调试方式,只是我不知道。
看看 Chrome 的官方文档怎么介绍的:
Chromium does not support @require, @resource, unsafeWindow, GM_registerMenuCommand, GM_setValue, or GM_getValue.
GM_xmlhttpRequest is same-origin only.
这个显然有点过时。根据最近被完成的这个 issue,现在 GM_xmlhttpRequest 已经可以跨域请求了。
关键的一点,Chrome 不支持 @require 的写法,这可是个很方便的功能啊。我对 jQuery 相对熟悉一点,几乎每个脚本里都会用这个命令把 jQuery 引入。这里有个例子介绍怎么解决这个问题。再稍稍加工一下,就得到一个跨浏览器的引入多个外部 JavaScript 的方案:
跨浏览器 @require
// ==UserScript==
// @name ???
// @namespace http://your.tld/
// ==/UserScript==
var scripts = [
'//cdnjs.cloudflare.com/ajax/libs/jquery/1.7/jquery.min.js',
'//www.readability.com/embed.js'
];
var numScripts = scripts.length, loadedScripts = 0;
GM_addStyle('CSS styles goes here');
function main() {
jQuery.noConflict(); // if window.$ has been used by other libs
// ...
}
var i, protocol = document.location.protocol;
for (i = 0; i < numScripts; i++) {
var script = document.createElement("script");
script.setAttribute("src", protocol + scripts[i]);
script.addEventListener('load', function() {
loadedScripts += 1;
if (loadedScripts < numScripts) {
return;
}
var script = document.createElement("script");
script.textContent = "(" + main.toString() + ")();";
document.body.appendChild(script);
}, false);
document.body.appendChild(script);
console.log(script);
}
在这个方案里,我把主要的逻辑都放在 main 函数里。需要搞清楚的是,main 不会在这个扩展脚本的 scope 里运行!它实际上是作为页面里内嵌的一段 JavaScript 代码被执行了,所以不要在里面引用任何所谓“全局变量”(不过可以引用你所确定知道的页面 window 对象的属性)。所以它不能调用 GM_addStyle, 于是我放在 main 外面。需要 Ajax 的话,也只能使用普通的 XMLHttpRequest 对象,也就不能进行跨域数据请求了,这在某些脚本里可能无法忍受。
iframe
在 Firefox 的 Greasemonkey 里,如果你在文件头用 @include 声明一个 URL 格式,只要符合要求,不管是正常网页还是 iframe,脚本都会执行。但是 Chrome 不会在 iframe 里执行它!
当然这个问题也有解决办法。比如 Gmail 最上面的菜单栏是在一个 iframe 里的,用这种方式获取:
window.frames['canvas_frame'].contentDocument.getElementById('gbz')
注意这里用的是 contentDocument. Chrome 不支持 contentWindow.document.
其它问题
我还遇到一个小问题是在使用 GM_xmlhttpRequest 设定 User-Agent 的时候。Chrome 报错:
Refused to set unsafe header “User-agent”
想了解更多请看我最近写的两个实用的 user scripts:
- Google Reader Unread Count in Gmail – 使用了 GM_xmlhttpRequest,并且操作是在 iframe 里发生的
- Google Reader Readability – 引入了两个外部脚本
这两个脚本都很简短,并且同时支持 Firefox 和 Chrome.
Leave a Reply