2

启动 Firefox、加载第 3 方网站(我被授权“自动化”)并针对该网站运行一些“特权”API 的最简单方法是什么?(例如:nsIProgressListener、nsIWindowMediator 等)。

我尝试了两种方法:

  1. 使用 XULrunner 创建一个选项卡式浏览器,“探测”第 3 方站点打开新窗口、遵循 302 重定向等所需的所有适当 API。这样做会产生大量代码,并且需要(afaict)用户安装应用程序,或使用 -app 运行 Firefox。它也非常脆弱。:-/

  2. 启动 Firefox 传递第 3 方站点的 URL,MozRepl 已经在监听。然后在启动后不久,从“启动”脚本远程登录到 MozRepl,使用 mozIJSSubScriptLoader::loadSubScript 加载我的代码,然后在 3rd 方站点的上下文中从 MozRepl 执行我的代码 -这是我目前正在做的方式它

使用第一种方法,我遇到了很多安全问题(显然)要解决,而且似乎我编写的浏览器“管道”代码比自动化代码多 10 倍。

使用第二种方法,我看到了很多“时间问题”,即:

  • MozRepl(或执行我提供的特权代码)以某种方式阻止了第 3 方站点的加载???,或
  • 3rd 方站点加载,但 MozRepl 执行的代码没有看到它加载,或者
  • 3rd 方站点加载,并且 MozRepl 还没有准备好接受请求(尽管页面中运行了其他 JavaScript,并且端口 4242 被 Firefox 进程绑定),
  • 等等

我想也许做这样的事情:

以某种方式修改 MozRepl 源,以便在启动时从文件系统中可预测的位置加载特权 JavaScript(或与 Firefox 命令行参数交互)并在第 3 方网站的上下文中执行它。

...甚至编写另一个更专注于该任务的类似附加组件。

有更简单的想法吗?


更新:

经过大量的反复试验,回答了我自己的问题(如下)。

4

1 回答 1

2

我发现最简单的方法是编写一个专门构建的 Firefox 扩展!

第 1 步。我不想做一堆不必要的 XUL/插件相关的东西;“Bootstrapped”(或重新启动)扩展只需要一个install.rdf文件来识别插件,以及一个bootstrap.js文件来实现引导接口。

bootstrap 接口可以非常简单地实现:

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
  • 创建一个新的配置文件,指定易于重复使用的位置(我称为 mine testing-profile),然后退出配置文件管理器。
  • 从用户的 mozilla 配置中删除新的配置文件profiles.ini(这样它就不会干扰正常浏览)。
  • 使用该配置文件启动 Firefox:firefox -profile /path/to/testing-profile
  • 从文件系统(而不是 addons.mozilla.org)安装扩展。
  • 执行准备配置文件所需的任何其他操作。(例如:我需要添加第 3 方证书并允许相关域的弹出窗口。)
  • 保持单个about:blank选项卡处于打开状态,然后退出 Firefox。
  • 快照配置文件:tar cvf testing-profile-snapshot.tar /path/to/testing-profile

从那时起,每次我运行自动化时,我都会解压testing-profile-snapshot.tar现有testing-profile文件夹并运行firefox -profile /path/to/testing-profile about:blank以使用“原始”配置文件。

第 3 步。所以现在当我启动 Firefox 时,它会在每次启动testing-profile时“包含”外部代码。/PATH/TO/EXTERNAL/CODE.js

注意:我发现我必须在上面的第2/PATH/TO/EXTERNAL/步中将文件夹移动到其他位置,因为外部 JavaScript 代码将被缓存(!!! - 在开发过程中不受欢迎)在配置文件中(即:不会看到对外部代码的更改下次发射时)。

外部代码具有特权,可以使用任何 Mozilla 平台 API。然而,有一个时间问题。包含(并因此执行)外部代码的时刻是DOMWindow尚不存在 Chrome 窗口对象(因此不存在对象)的时刻。

所以我们需要等待,直到有一个有用的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 步。所有这些代码都在“全局”范围内执行。如果您稍后需要加载其他 JavaScript 文件(例如:jQuery),请在(全局!)范围loadSubscript内显式调用null

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.$;
}

现在我们可以通过将第二个参数传递给选择器调用来使用jQuery任何对象!DOMWindow<DOMWindow>.document

于 2013-01-08T03:19:11.640 回答