Самый простой способ запустить Firefox - управлять сторонним сайтом с помощью привилегированных API nsI*.
Какой самый простой способ запустить Firefox, загрузить сторонний веб-сайт (который я уполномочен "автоматизировать") и запустить некоторые "привилегированные" API для этого сайта? (например: nsIProgressListener, nsIWindowMediator и т. д.).
Я попробовал два подхода:
Создайте браузер с вкладками с помощью XULrunner, "подключив" все соответствующие API-интерфейсы, необходимые для стороннего сайта, чтобы открывать новые окна, выполнять перенаправления 302 и т. Д. Делая это таким образом, это ужасно много кода и требует (на самом деле), что Пользователь устанавливает приложение или запускает Firefox с -app. Это также чрезвычайно хрупко.:-/
Запустите 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
файл для реализации интерфейса начальной загрузки.
Загрузочное расширение: 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
), а затем выйдите из диспетчера профилей. - Удалить новый профиль из
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
В качестве второго параметра для вызова селектора!