启动Firefox,加载第三方网站(我授权"自动化"),并对该网站运行一些"特权"api的最简单方法是什么?(例如:nsIProgressListener, nsIWindowMediator等)。
我试过两种方法:
-
使用XULrunner创建一个选项卡浏览器,"管道"第三方站点打开新窗口所需的所有适当api,遵循302重定向等。这样做需要大量的代码,并且需要用户安装应用程序,或者运行带有-app的Firefox。它也非常脆弱。: -/
-
启动Firefox传递第三方站点的URL, MozRepl已经在监听。然后在启动后不久,从"启动"脚本telnet到MozRepl,使用mozIJSSubScriptLoader::loadSubScript加载我的代码,然后在第三方网站的上下文中从MozRepl执行我的代码——这是我目前正在做的方式
使用第一种方法,我有很多安全问题(显然)需要解决,而且看起来我编写的浏览器"管道"代码比自动化代码多10倍。
对于第二种方法,我看到了很多"时间问题",即:
- 第三方网站以某种方式阻止MozRepl加载(或执行我提供的特权代码)??,或者
- 第三方网站加载,但MozRepl执行的代码没有看到它加载,或者
- 第三方网站加载,MozRepl还没有准备好接受请求(尽管页面中运行其他JavaScript,端口4242被Firefox进程绑定),
- 等。
我想也许可以这样做:
以某种方式修改MozRepl源代码,以便在启动时从文件系统中可预测的位置加载特权JavaScript(或与Firefox命令行参数交互),并在第三方网站的上下文中执行。
…或者甚至编写另一个类似的插件,它更专注于任务。
有更简单的想法吗?
更新:
经过反复试验,回答了我自己的问题(如下)
我发现最简单的方法是编写一个专门构建的Firefox扩展!
步骤1。我不想做一堆不必要的XUL/插件相关的东西,这是没有必要的;一个"引导"(或重新启动)的扩展只需要一个install.rdf
文件来识别插件,一个bootstrap.js
文件来实现引导接口。
-
bootstrap Extension: https://developer.mozilla.org/en-US/docs/Extensions/Bootstrapped_extensions
-
示例:http://blog.fpmurphy.com/2011/02/firefox-4-restartless-add-ons.html
引导接口可以非常简单地实现:
const path = '/PATH/TO/EXTERNAL/CODE.js';
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
var loaderSvc = Cc["@mozilla.org/moz/jssubscript-loader;1"];
.getService(Ci.mozIJSSubScriptLoader);
function install() {}
function uninstall() {}
function shutdown(data, reason) {}
function startup(data, reason) { loaderSvc.loadSubScript("file://"+path); }
通过将install.rdf
和bootstrap.js
放入新zip文件的顶层来编译扩展名,并将zip文件扩展名重命名为.xpi
。
步骤2。有可重复的生产环境& &;经过测试,我发现最简单的方法是用一个专门用于自动化任务的配置文件启动Firefox:
- 启动Firefox配置文件管理器:
firefox -ProfileManager
- 创建一个新的配置文件,指定位置便于重用(我调用我的
testing-profile
),然后退出配置文件管理器。 - 从用户的mozilla配置中的
profiles.ini
中删除新的配置文件(以便它不会干扰正常的浏览)。 - 使用配置文件启动Firefox:
firefox -profile /path/to/testing-profile
- 从文件系统(而不是addons.mozilla.org)安装扩展。
- 做任何其他需要准备配置文件的事情。(例如:我需要添加第三方证书,并允许相关域的弹出窗口。)
- 打开
about:blank
选项卡,然后退出Firefox。 - 快照配置文件:
tar cvf testing-profile-snapshot.tar /path/to/testing-profile
从那时起,每次我运行自动化时,我都会在现有的testing-profile
文件夹上解压缩testing-profile-snapshot.tar
,并运行firefox -profile /path/to/testing-profile about:blank
以使用"原始"配置文件。
步骤3。所以现在当我用testing-profile
启动Firefox时,它将在每次启动时"包含"/PATH/TO/EXTERNAL/CODE.js
的外部代码。
注意:我发现我必须在上面的步骤2期间将/PATH/TO/EXTERNAL/
文件夹移动到其他地方,因为外部JavaScript代码将被缓存(!—在开发期间不需要)在配置文件中(即:对外部代码的更改将不会在下次发布时看到)。
外部代码具有特权,可以使用任何Mozilla平台api。但是存在计时的问题。外部代码被包含(并因此执行)的时刻是不存在Chrome窗口对象(因此不存在DOMWindow
对象)的时刻。
DOMWindow
对象:
// useful services.
Cu.import("resource://gre/modules/Services.jsm");
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
var wmSvc = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
var logSvc = Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
// "user" code entry point.
function user_code() {
// your code here!
// window, gBrowser, etc work as per MozRepl!
}
// get the gBrowser, first (about:blank) domWindow,
// and set up common globals.
var done_startup = 0;
var windowListener;
function do_startup(win) {
if (done_startup) return;
done_startup = 1;
wm.removeListener(windowListener);
var browserEnum = wm.getEnumerator("navigator:browser");
var browserWin = browserEnum.getNext();
var tabbrowser = browserWin.gBrowser;
var currentBrowser = tabbrowser.getBrowserAtIndex(0);
var domWindow = currentBrowser.contentWindow;
window = domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
gBrowser = window.gBrowser;
setTimeout = window.setTimeout;
setInterval = window.setInterval;
alert = function(message) {
Services.prompt.alert(null, "alert", message);
};
console = {
log: function(message) {
logSvc.logStringMessage(message);
}
};
// the first domWindow will finish loading a little later than gBrowser...
gBrowser.addEventListener('load', function() {
gBrowser.removeEventListener('load', arguments.callee, true);
user_code();
}, true);
}
// window listener implementation
windowListener = {
onWindowTitleChange: function(aWindow, aTitle) {},
onCloseWindow: function(aWindow) {},
onOpenWindow: function(aWindow) {
var win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
win.addEventListener("load", function(aEvent) {
win.removeEventListener("load", arguments.callee, false);
if (aEvent.originalTarget.nodeName != "#document") return;
do_startup();
}
};
// CODE ENTRY POINT!
wm.addListener(windowListener);
步骤4。所有这些代码都在"global"作用域中执行。如果你以后需要加载其他JavaScript文件(例如:jQuery),在null
(全局!)范围内显式调用loadSubscript
function some_user_code() {
loader.loadSubScript.call(null,"file:///PATH/TO/SOME/CODE.js");
loader.loadSubScript.call(null,"http://HOST/PATH/TO/jquery.js");
$ = jQuery = window.$;
}
现在我们可以通过将<DOMWindow>.document
作为第二个参数传递给选择器调用来在任何DOMWindow
上使用jQuery
!