Самый простой способ запустить Firefox - управлять сторонним сайтом с помощью привилегированных API nsI*.

Какой самый простой способ запустить Firefox, загрузить сторонний веб-сайт (который я уполномочен "автоматизировать") и запустить некоторые "привилегированные" API для этого сайта? (например: nsIProgressListener, nsIWindowMediator и т. д.).

Я попробовал два подхода:

  1. Создайте браузер с вкладками с помощью XULrunner, "подключив" все соответствующие API-интерфейсы, необходимые для стороннего сайта, чтобы открывать новые окна, выполнять перенаправления 302 и т. Д. Делая это таким образом, это ужасно много кода и требует (на самом деле), что Пользователь устанавливает приложение или запускает Firefox с -app. Это также чрезвычайно хрупко.:-/

  2. Запустите Firefox, передавая URL-адрес стороннего сайта, а MozRepl уже прослушивает. Затем, вскоре после запуска, telnet из скрипта запуска в MozRepl, используйте mozIJSSubScriptLoader::loadSubScript для загрузки моего кода, затем выполните мой код из MozRepl в контексте стороннего сайта - так я сейчас и поступаю это

При первом подходе у меня возникает множество проблем с безопасностью (очевидно), и мне кажется, что я пишу в 10 раз больше кода, "подключаемого" браузером, чем кода автоматизации.

При втором подходе я вижу много "проблем с синхронизацией", а именно:

  • стороннему сайту каким-либо образом запрещена загрузка MozRepl (или выполнение предоставленного мной привилегированного кода)???, или
  • сторонний сайт загружается, но код, выполняемый MozRepl, не видит его загрузки, или
  • сторонний сайт загружается, и MozRepl не готов принимать запросы (несмотря на то, что другой JavaScript-код работает на странице, а порт 4242 связан процессом Firefox),
  • и т.п.

Я думал о том, чтобы сделать что-то вроде этого:

Модифицируйте исходный код MozRepl, чтобы загрузить привилегированный JavaScript-код из предсказуемого места в файловой системе при запуске (или взаимодействовать с аргументами командной строки Firefox) и выполнить его в контексте стороннего веб-сайта.

... или даже написать другое подобное дополнение, которое больше предназначено для этой задачи.

Есть более простые идеи?


Обновить:

После долгих проб и ошибок ответил на свой вопрос (ниже).

1 ответ

Решение

Я обнаружил, что самым простым способом было написать специально разработанное расширение для Firefox!

Шаг 1. Я не хотел делать кучу ненужных вещей, связанных с XUL/ аддонами, которые не были нужны; Расширению "Bootstrapped" (или перезапуску) требуется только install.rdf файл для идентификации аддона и bootstrap.js файл для реализации интерфейса начальной загрузки.

Интерфейс начальной загрузки может быть реализован очень просто:

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), а затем выйдите из диспетчера профилей.
  • Удалить новый профиль из profiles.ini в конфигурации пользователя mozilla (чтобы он не мешал нормальному просмотру).
  • Запустите 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-snapshot.tar по существующему testing-profile папка и запустить firefox -profile /path/to/testing-profile about:blank использовать "нетронутый" профиль.

Шаг 3. Итак, теперь, когда я запускаю Firefox с testing-profile он будет "включать" внешний код в /PATH/TO/EXTERNAL/CODE.js на каждом запуске.

ПРИМЕЧАНИЕ: я обнаружил, что мне пришлось переместить /PATH/TO/EXTERNAL/ в другом месте на шаге 2 выше, поскольку внешний код JavaScript будет кэшироваться (!!! - нежелательно во время разработки) внутри профиля (то есть: изменения во внешнем коде не будут видны при следующем запуске).

Внешний код является привилегированным и может использовать любой из API-интерфейсов платформы Mozilla. Однако существует проблема времени. Момент времени, в который внешний код включен (и, следовательно, выполнен), является моментом, в который нет объектов окна 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. Весь этот код выполняется в "глобальной" области. Если вам позже потребуется загрузить другие файлы 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 В качестве второго параметра для вызова селектора!

Другие вопросы по тегам