Bug 1107706: Part 5: Refactor Marionette chrome/content communication

The Marionette server handles requests separately with a global sense
of state which makes it hard to introduce generalised behaviour to many
commands.  This effectively slows down protocol implementation because
each command request individually needs to do heavy lifting.

This patch introduces a series of abstractions that separates out the
WebDriver implementation to a new class, GeckoDriver.  It also features
a new interface to mediate messages between the chrome- and content
processes.

This allows the code living in the chrome context to make direct calls
on the listener through a promise-based API:

	let listener = new ListenerProxy(mm, sendCallback);
	let res = yield listener.functionOnListener("arg1", "arg2");

The MarionetteServer class that used to live in marionette-server.js
has now been moved to server.js, while the WebDriver implementation
has moved to driver.js.  By introducing more stringent separation,
MarionetteServer now properly encapsulates the server process allowing
us to unit tests for it in the future.

The patch is a refactor in the truest sense, in the meaning that no
input or output should have changed.

--HG--
extra : rebase_source : 72c68105df19dc1e328f78c6bfb2282b61d82c8d
extra : source : 7f506cdb77b88994ba9f5b13cc936a99a403f1fb
This commit is contained in:
Andreas Tolfsen 2015-03-19 21:12:58 +00:00
Родитель 2db8cf1de0
Коммит bd8f7a8359
10 изменённых файлов: 3747 добавлений и 4031 удалений

Просмотреть файл

@ -2,37 +2,34 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file, * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
this.CC = Components.Constructor; "use strict";
this.Cc = Components.classes;
this.Ci = Components.interfaces; const {Constructor: CC, interfaces: Ci, utils: Cu} = Components;
this.Cu = Components.utils;
this.Cr = Components.results;
const MARIONETTE_CONTRACTID = "@mozilla.org/marionette;1"; const MARIONETTE_CONTRACTID = "@mozilla.org/marionette;1";
const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}"); const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
const MARIONETTE_ENABLED_PREF = 'marionette.defaultPrefs.enabled';
const MARIONETTE_FORCELOCAL_PREF = 'marionette.force-local';
const MARIONETTE_LOG_PREF = 'marionette.logging';
this.ServerSocket = CC("@mozilla.org/network/server-socket;1", const DEFAULT_PORT = 2828;
"nsIServerSocket", const ENABLED_PREF = "marionette.defaultPrefs.enabled";
"initSpecialConnection"); const PORT_PREF = "marionette.defaultPrefs.port";
const FORCELOCAL_PREF = "marionette.force-local";
const LOG_PREF = "marionette.logging";
const ServerSocket = CC("@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"initSpecialConnection");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://gre/modules/Log.jsm");
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
function MarionetteComponent() { function MarionetteComponent() {
this._loaded = false; this.loaded_ = false;
this.observerService = Services.obs; this.observerService = Services.obs;
// set up the logger
this.logger = Log.repository.getLogger("Marionette"); this.logger = Log.repository.getLogger("Marionette");
this.logger.level = Log.Level["Trace"]; this.logger.level = Log.Level.Trace;
let dumper = false; let dumper = false;
#ifdef DEBUG #ifdef DEBUG
dumper = true; dumper = true;
@ -41,12 +38,11 @@ function MarionetteComponent() {
dumper = true; dumper = true;
#endif #endif
try { try {
if (dumper || Services.prefs.getBoolPref(MARIONETTE_LOG_PREF)) { if (dumper || Services.prefs.getBoolPref(LOG_PREF)) {
let formatter = new Log.BasicFormatter(); let formatter = new Log.BasicFormatter();
this.logger.addAppender(new Log.DumpAppender(formatter)); this.logger.addAppender(new Log.DumpAppender(formatter));
} }
} } catch(e) {}
catch(e) {}
} }
MarionetteComponent.prototype = { MarionetteComponent.prototype = {
@ -54,134 +50,141 @@ MarionetteComponent.prototype = {
classID: MARIONETTE_CID, classID: MARIONETTE_CID,
contractID: MARIONETTE_CONTRACTID, contractID: MARIONETTE_CONTRACTID,
QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler, Ci.nsIObserver]), QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler, Ci.nsIObserver]),
_xpcom_categories: [{category: "command-line-handler", entry: "b-marionette"}, _xpcom_categories: [
{category: "profile-after-change", service: true}], {category: "command-line-handler", entry: "b-marionette"},
appName: Services.appinfo.name, {category: "profile-after-change", service: true}
],
enabled: false, enabled: false,
finalUiStartup: false, finalUiStartup: false,
_marionetteServer: null, server: null
};
onSocketAccepted: function mc_onSocketAccepted(aSocket, aTransport) { MarionetteComponent.prototype.onSocketAccepted = function(
this.logger.info("onSocketAccepted for Marionette dummy socket"); socket, transport) {
}, this.logger.info("onSocketAccepted for Marionette dummy socket");
};
onStopListening: function mc_onStopListening(aSocket, status) { MarionetteComponent.prototype.onStopListening = function(socket, status) {
this.logger.info("onStopListening for Marionette dummy socket, code " + status); this.logger.info(`onStopListening for Marionette dummy socket, code ${status}`);
aSocket.close(); socket.close();
}, };
// Check cmdLine argument for --marionette /** Check cmdLine argument for {@code --marionette}. */
handle: function mc_handle(cmdLine) { MarionetteComponent.prototype.handle = function(cmdLine) {
// If the CLI is there then lets do work otherwise nothing to see // if the CLI is there then lets do work otherwise nothing to see
if (cmdLine.handleFlag("marionette", false)) { if (cmdLine.handleFlag("marionette", false)) {
this.enabled = true; this.enabled = true;
this.logger.info("marionette enabled via command-line"); this.logger.info("Marionette enabled via command-line flag");
this.init(); this.init();
} }
}, };
observe: function mc_observe(aSubject, aTopic, aData) { MarionetteComponent.prototype.observe = function(subj, topic, data) {
switch (aTopic) { switch (topic) {
case "profile-after-change": case "profile-after-change":
// Using final-ui-startup as the xpcom category doesn't seem to work, // Using final-ui-startup as the xpcom category doesn't seem to work,
// so we wait for that by adding an observer here. // so we wait for that by adding an observer here.
this.observerService.addObserver(this, "final-ui-startup", false); this.observerService.addObserver(this, "final-ui-startup", false);
#ifdef ENABLE_MARIONETTE #ifdef ENABLE_MARIONETTE
let enabledPref = false; try {
try { this.enabled = Services.prefs.getBoolPref(ENABLED_PREF);
enabledPref = Services.prefs.getBoolPref(MARIONETTE_ENABLED_PREF); } catch(e) {}
} catch(e) {} if (this.enabled) {
if (enabledPref) { this.logger.info("Marionette enabled via build flag and pref");
this.enabled = true;
this.logger.info("marionette enabled via build flag and pref");
// We want to suppress the modal dialog that's shown // We want to suppress the modal dialog that's shown
// when starting up in safe-mode to enable testing. // when starting up in safe-mode to enable testing.
if (Services.appinfo.inSafeMode) { if (Services.appinfo.inSafeMode) {
this.observerService.addObserver(this, "domwindowopened", false); this.observerService.addObserver(this, "domwindowopened", false);
}
} }
}
#endif #endif
break; break;
case "final-ui-startup":
this.finalUiStartup = true; case "final-ui-startup":
this.observerService.removeObserver(this, aTopic); this.finalUiStartup = true;
this.observerService.addObserver(this, "xpcom-shutdown", false); this.observerService.removeObserver(this, topic);
this.init(); this.observerService.addObserver(this, "xpcom-shutdown", false);
break; this.init();
case "domwindowopened": break;
this.observerService.removeObserver(this, aTopic);
this._suppressSafeModeDialog(aSubject); case "domwindowopened":
break; this.observerService.removeObserver(this, topic);
case "xpcom-shutdown": this.suppressSafeModeDialog_(subj);
this.observerService.removeObserver(this, "xpcom-shutdown"); break;
this.uninit();
break; case "xpcom-shutdown":
this.observerService.removeObserver(this, "xpcom-shutdown");
this.uninit();
break;
}
};
MarionetteComponent.prototype.suppressSafeModeDialog_ = function(win) {
// Wait for the modal dialog to finish loading.
win.addEventListener("load", function onload() {
win.removeEventListener("load", onload);
if (win.document.getElementById("safeModeDialog")) {
// Accept the dialog to start in safe-mode
win.setTimeout(() => {
win.document.documentElement.getButton("accept").click();
});
} }
}, });
};
_suppressSafeModeDialog: function mc_suppressSafeModeDialog(aWindow) { MarionetteComponent.prototype.init = function() {
// Wait for the modal dialog to finish loading. if (this.loaded_ || !this.enabled || !this.finalUiStartup) {
aWindow.addEventListener("load", function onLoad() { return;
aWindow.removeEventListener("load", onLoad); }
if (aWindow.document.getElementById("safeModeDialog")) { this.loaded_ = true;
aWindow.setTimeout(() => {
// Accept the dialog to start in safe-mode.
aWindow.document.documentElement.getButton("accept").click();
});
}
});
},
init: function mc_init() { let forceLocal = Services.appinfo.name == "B2G" ? false : true;
if (!this._loaded && this.enabled && this.finalUiStartup) { try {
this._loaded = true; forceLocal = Services.prefs.getBoolPref(FORCELOCAL_PREF);
} catch (e) {}
Services.prefs.setBoolPref(FORCELOCAL_PREF, forceLocal);
let marionette_forcelocal = this.appName == 'B2G' ? false : true; if (!forceLocal) {
try { // See bug 800138. Because the first socket that opens with
marionette_forcelocal = Services.prefs.getBoolPref(MARIONETTE_FORCELOCAL_PREF); // force-local=false fails, we open a dummy socket that will fail.
} // keepWhenOffline=true so that it still work when offline (local).
catch(e) {} // This allows the following attempt by Marionette to open a socket
Services.prefs.setBoolPref(MARIONETTE_FORCELOCAL_PREF, marionette_forcelocal); // to succeed.
let insaneSacrificialGoat =
new ServerSocket(666, Ci.nsIServerSocket.KeepWhenOffline, 4);
insaneSacrificialGoat.asyncListen(this);
}
if (!marionette_forcelocal) { let port = DEFAULT_PORT;
// See bug 800138. Because the first socket that opens with try {
// force-local=false fails, we open a dummy socket that will fail. port = Services.prefs.getIntPref(PORT_PREF);
// keepWhenOffline=true so that it still work when offline (local). } catch (e) {}
// This allows the following attempt by Marionette to open a socket
// to succeed.
let insaneSacrificialGoat = new ServerSocket(666, Ci.nsIServerSocket.KeepWhenOffline, 4);
insaneSacrificialGoat.asyncListen(this);
}
let port; let s;
try { try {
port = Services.prefs.getIntPref('marionette.defaultPrefs.port'); Cu.import("chrome://marionette/content/server.js");
} s = new MarionetteServer(port, forceLocal);
catch(e) { s.start();
port = 2828; this.logger.info(`Listening on port ${s.port}`);
} } catch (e) {
try { this.logger.error(`Error on starting server: ${e}`);
loader.loadSubScript("chrome://marionette/content/marionette-server.js"); dump(e.toString() + "\n" + e.stack + "\n");
let forceLocal = Services.prefs.getBoolPref(MARIONETTE_FORCELOCAL_PREF); } finally {
this._marionetteServer = new MarionetteServer(port, forceLocal); if (s) {
this.logger.info("Marionette server ready"); this.server = s;
}
catch(e) {
this.logger.error('exception: ' + e.name + ', ' + e.message + ': ' +
e.fileName + " :: " + e.lineNumber);
}
} }
}, }
};
uninit: function mc_uninit() {
if (this._marionetteServer) {
this._marionetteServer.closeListener();
}
this._loaded = false;
},
MarionetteComponent.prototype.uninit = function() {
if (!this.loaded_) {
return;
}
this.server.stop();
this.loaded_ = false;
}; };
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]); this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]);

Просмотреть файл

@ -244,6 +244,7 @@ Dispatcher.prototype.sendToClient = function(payload, cmdId) {
logger.warn(`Ignoring out-of-sync response with command ID: ${cmdId}`); logger.warn(`Ignoring out-of-sync response with command ID: ${cmdId}`);
return; return;
} }
this.driver.responseCompleted();
this.sendRaw("client", payload); this.sendRaw("client", payload);
}; };

3305
testing/marionette/driver.js Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -4,7 +4,8 @@
marionette.jar: marionette.jar:
% content marionette %content/ % content marionette %content/
content/marionette-server.js (marionette-server.js) content/server.js (server.js)
content/driver.js (driver.js)
content/marionette-listener.js (marionette-listener.js) content/marionette-listener.js (marionette-listener.js)
content/marionette-elements.js (marionette-elements.js) content/marionette-elements.js (marionette-elements.js)
content/marionette-sendkeys.js (marionette-sendkeys.js) content/marionette-sendkeys.js (marionette-sendkeys.js)

Просмотреть файл

@ -427,18 +427,18 @@ ElementManager.prototype = {
* as the start node instead of the document root * as the start node instead of the document root
* If this object has a 'time' member, this number will be * If this object has a 'time' member, this number will be
* used to see if we have hit the search timelimit. * used to see if we have hit the search timelimit.
* @param function on_success
* The notification callback used when we are returning successfully.
* @param function on_error
The callback to invoke when an error occurs.
* @param boolean all * @param boolean all
* If true, all found elements will be returned. * If true, all found elements will be returned.
* If false, only the first element will be returned. * If false, only the first element will be returned.
* @param function on_success
* Callback used when operating is successful.
* @param function on_error
* Callback to invoke when an error occurs.
* *
* @return nsIDOMElement or list of nsIDOMElements * @return nsIDOMElement or list of nsIDOMElements
* Returns the element(s) by calling the on_success function. * Returns the element(s) by calling the on_success function.
*/ */
find: function EM_find(win, values, searchTimeout, on_success, on_error, all, command_id) { find: function EM_find(win, values, searchTimeout, all, on_success, on_error, command_id) {
let startTime = values.time ? values.time : new Date().getTime(); let startTime = values.time ? values.time : new Date().getTime();
let startNode = (values.element != undefined) ? let startNode = (values.element != undefined) ?
this.getKnownElement(values.element, win) : win.document; this.getKnownElement(values.element, win) : win.document;
@ -461,13 +461,13 @@ ElementManager.prototype = {
} else if (values.using == ANON_ATTRIBUTE) { } else if (values.using == ANON_ATTRIBUTE) {
message = "Unable to locate anonymous element: " + JSON.stringify(values.value); message = "Unable to locate anonymous element: " + JSON.stringify(values.value);
} }
on_error(message, 7, null, command_id); on_error({message: message, code: 7}, command_id);
} }
} else { } else {
values.time = startTime; values.time = startTime;
this.timer.initWithCallback(this.find.bind(this, win, values, this.timer.initWithCallback(this.find.bind(this, win, values,
searchTimeout, searchTimeout, all,
on_success, on_error, all, on_success, on_error,
command_id), command_id),
100, 100,
Components.interfaces.nsITimer.TYPE_ONE_SHOT); Components.interfaces.nsITimer.TYPE_ONE_SHOT);
@ -476,14 +476,13 @@ ElementManager.prototype = {
if (isArrayLike) { if (isArrayLike) {
let ids = [] let ids = []
for (let i = 0 ; i < found.length ; i++) { for (let i = 0 ; i < found.length ; i++) {
ids.push({'ELEMENT': this.addToKnownElements(found[i])}); ids.push({"ELEMENT": this.addToKnownElements(found[i])});
} }
on_success(ids, command_id); on_success(ids, command_id);
} else { } else {
let id = this.addToKnownElements(found); let id = this.addToKnownElements(found);
on_success({'ELEMENT':id}, command_id); on_success({"ELEMENT": id}, command_id);
} }
return;
} }
}, },

Просмотреть файл

@ -2,17 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file, * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = [ let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
"FrameManager"
]; this.EXPORTED_SYMBOLS = ["FrameManager"];
let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js"; let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
let logger = Log.repository.getLogger("Marionette");
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader); .getService(Ci.mozIJSSubScriptLoader);
let specialpowers = {}; let specialpowers = {};
@ -99,30 +97,43 @@ FrameManager.prototype = {
} }
}, },
//This is just 'switch to OOP frame'. We're handling this here so we can maintain a list of remoteFrames. getOopFrame: function FM_getOopFrame(winId, frameId) {
switchToFrame: function FM_switchToFrame(message) { // get original frame window
// Switch to a remote frame. let outerWin = Services.wm.getOuterWindowWithId(winId);
let frameWindow = Services.wm.getOuterWindowWithId(message.json.win); //get the original frame window // find the OOP frame
let oopFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame]; //find the OOP frame let f = outerWin.document.getElementsByTagName("iframe")[frameId];
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; //get the OOP frame's mm return f;
},
getFrameMM: function FM_getFrameMM(winId, frameId) {
let oopFrame = this.getOopFrame(winId, frameId);
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
return mm;
},
/**
* Switch to OOP frame. We're handling this here
* so we can maintain a list of remote frames.
*/
switchToFrame: function FM_switchToFrame(winId, frameId) {
let oopFrame = this.getOopFrame(winId, frameId);
let mm = this.getFrameMM(winId, frameId);
if (!specialpowers.hasOwnProperty("specialPowersObserver")) { if (!specialpowers.hasOwnProperty("specialPowersObserver")) {
loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
specialpowers); specialpowers);
} }
// See if this frame already has our frame script loaded in it; if so, // See if this frame already has our frame script loaded in it;
// just wake it up. // if so, just wake it up.
for (let i = 0; i < remoteFrames.length; i++) { for (let i = 0; i < remoteFrames.length; i++) {
let frame = remoteFrames[i]; let frame = remoteFrames[i];
let frameMessageManager = frame.messageManager.get(); let frameMessageManager = frame.messageManager.get();
logger.info("trying remote frame " + i);
try { try {
frameMessageManager.sendAsyncMessage("aliveCheck", {}); frameMessageManager.sendAsyncMessage("aliveCheck", {});
} } catch (e) {
catch(e) {
if (e.result == Components.results.NS_ERROR_NOT_INITIALIZED) { if (e.result == Components.results.NS_ERROR_NOT_INITIALIZED) {
logger.info("deleting frame");
remoteFrames.splice(i, 1); remoteFrames.splice(i, 1);
continue; continue;
} }
@ -130,29 +141,31 @@ FrameManager.prototype = {
if (frameMessageManager == mm) { if (frameMessageManager == mm) {
this.currentRemoteFrame = frame; this.currentRemoteFrame = frame;
this.addMessageManagerListeners(mm); this.addMessageManagerListeners(mm);
if (!frame.specialPowersObserver) { if (!frame.specialPowersObserver) {
frame.specialPowersObserver = new specialpowers.SpecialPowersObserver(); frame.specialPowersObserver = new specialpowers.SpecialPowersObserver();
frame.specialPowersObserver.init(mm); frame.specialPowersObserver.init(mm);
} }
mm.sendAsyncMessage("Marionette:restart", {}); mm.sendAsyncMessage("Marionette:restart");
return oopFrame.id; return oopFrame.id;
} }
} }
// If we get here, then we need to load the frame script in this frame, // If we get here, then we need to load the frame script in this frame,
// and set the frame's ChromeMessageSender as the active message manager the server will listen to // and set the frame's ChromeMessageSender as the active message manager
// the server will listen to.
this.addMessageManagerListeners(mm); this.addMessageManagerListeners(mm);
let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame); let aFrame = new MarionetteRemoteFrame(winId, frameId);
aFrame.messageManager = Cu.getWeakReference(mm); aFrame.messageManager = Cu.getWeakReference(mm);
remoteFrames.push(aFrame); remoteFrames.push(aFrame);
this.currentRemoteFrame = aFrame; this.currentRemoteFrame = aFrame;
logger.info("frame-manager load script: " + mm.toString());
mm.loadFrameScript(FRAME_SCRIPT, true, true); mm.loadFrameScript(FRAME_SCRIPT, true, true);
aFrame.specialPowersObserver = new specialpowers.SpecialPowersObserver(); aFrame.specialPowersObserver = new specialpowers.SpecialPowersObserver();
aFrame.specialPowersObserver.init(mm); aFrame.specialPowersObserver.init(mm);
return oopFrame.id; return oopFrame.id;
}, },
@ -184,64 +197,58 @@ FrameManager.prototype = {
}, },
/** /**
* Adds message listeners to the server, listening for messages from content frame scripts. * Adds message listeners to the server,
* It also adds a "MarionetteFrame:getInterruptedState" message listener to the FrameManager, * listening for messages from content frame scripts.
* so the frame manager's state can be checked by the frame * It also adds a MarionetteFrame:getInterruptedState
* message listener to the FrameManager,
* so the frame manager's state can be checked by the frame.
* *
* @param object messageManager * @param {nsIMessageListenerManager} mm
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender) * The message manager object, typically
* to which the listeners should be added. * ChromeMessageBroadcaster or ChromeMessageSender.
*/ */
addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) { addMessageManagerListeners: function FM_addMessageManagerListeners(mm) {
messageManager.addWeakMessageListener("Marionette:ok", this.server); mm.addWeakMessageListener("Marionette:emitTouchEvent", this.server);
messageManager.addWeakMessageListener("Marionette:done", this.server); mm.addWeakMessageListener("Marionette:log", this.server);
messageManager.addWeakMessageListener("Marionette:error", this.server); mm.addWeakMessageListener("Marionette:runEmulatorCmd", this.server);
messageManager.addWeakMessageListener("Marionette:emitTouchEvent", this.server); mm.addWeakMessageListener("Marionette:runEmulatorShell", this.server);
messageManager.addWeakMessageListener("Marionette:log", this.server); mm.addWeakMessageListener("Marionette:shareData", this.server);
messageManager.addWeakMessageListener("Marionette:register", this.server); mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
messageManager.addWeakMessageListener("Marionette:runEmulatorCmd", this.server); mm.addWeakMessageListener("Marionette:switchedToFrame", this.server);
messageManager.addWeakMessageListener("Marionette:runEmulatorShell", this.server); mm.addWeakMessageListener("Marionette:addCookie", this.server);
messageManager.addWeakMessageListener("Marionette:shareData", this.server); mm.addWeakMessageListener("Marionette:getVisibleCookies", this.server);
messageManager.addWeakMessageListener("Marionette:switchToModalOrigin", this.server); mm.addWeakMessageListener("Marionette:deleteCookie", this.server);
messageManager.addWeakMessageListener("Marionette:switchToFrame", this.server); mm.addWeakMessageListener("Marionette:register", this.server);
messageManager.addWeakMessageListener("Marionette:switchedToFrame", this.server); mm.addWeakMessageListener("Marionette:listenersAttached", this.server);
messageManager.addWeakMessageListener("Marionette:addCookie", this.server); mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
messageManager.addWeakMessageListener("Marionette:getVisibleCookies", this.server); mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
messageManager.addWeakMessageListener("Marionette:deleteCookie", this.server); mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
messageManager.addWeakMessageListener("Marionette:listenersAttached", this.server);
messageManager.addWeakMessageListener("MarionetteFrame:handleModal", this);
messageManager.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
messageManager.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
}, },
/** /**
* Removes listeners for messages from content frame scripts. * Removes listeners for messages from content frame scripts.
* We do not remove the "MarionetteFrame:getInterruptedState" or the * We do not remove the MarionetteFrame:getInterruptedState
* "Marioentte:switchToModalOrigin" message listener, * or the Marionette:switchToModalOrigin message listener,
* because we want to allow all known frames to contact the frame manager so that * because we want to allow all known frames to contact the frame manager
* it can check if it was interrupted, and if so, it will call switchToModalOrigin * so that it can check if it was interrupted, and if so,
* when its process gets resumed. * it will call switchToModalOrigin when its process gets resumed.
* *
* @param object messageManager * @param {nsIMessageListenerManager} mm
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender) * The message manager object, typically
* from which the listeners should be removed. * ChromeMessageBroadcaster or ChromeMessageSender.
*/ */
removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) { removeMessageManagerListeners: function FM_removeMessageManagerListeners(mm) {
messageManager.removeWeakMessageListener("Marionette:ok", this.server); mm.removeWeakMessageListener("Marionette:log", this.server);
messageManager.removeWeakMessageListener("Marionette:done", this.server); mm.removeWeakMessageListener("Marionette:shareData", this.server);
messageManager.removeWeakMessageListener("Marionette:error", this.server); mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server);
messageManager.removeWeakMessageListener("Marionette:log", this.server); mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.server);
messageManager.removeWeakMessageListener("Marionette:shareData", this.server); mm.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
messageManager.removeWeakMessageListener("Marionette:register", this.server); mm.removeWeakMessageListener("Marionette:addCookie", this.server);
messageManager.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server); mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
messageManager.removeWeakMessageListener("Marionette:runEmulatorShell", this.server); mm.removeWeakMessageListener("Marionette:deleteCookie", this.server);
messageManager.removeWeakMessageListener("Marionette:switchToFrame", this.server); mm.removeWeakMessageListener("Marionette:listenersAttached", this.server);
messageManager.removeWeakMessageListener("Marionette:switchedToFrame", this.server); mm.removeWeakMessageListener("Marionette:register", this.server);
messageManager.removeWeakMessageListener("Marionette:addCookie", this.server); mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
messageManager.removeWeakMessageListener("Marionette:getVisibleCookies", this.server); mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
messageManager.removeWeakMessageListener("Marionette:deleteCookie", this.server); }
messageManager.removeWeakMessageListener("Marionette:listenersAttached", this.server);
messageManager.removeWeakMessageListener("MarionetteFrame:handleModal", this);
messageManager.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
},
}; };

Просмотреть файл

@ -107,9 +107,11 @@ function registerSelf() {
importedScripts = FileUtils.getDir('TmpD', [], false); importedScripts = FileUtils.getDir('TmpD', [], false);
importedScripts.append('marionetteContentScripts'); importedScripts.append('marionetteContentScripts');
startListeners(); startListeners();
let rv = {};
if (remotenessChange) { if (remotenessChange) {
sendAsyncMessage("Marionette:listenersAttached", {listenerId: id}); rv.listenerId = id;
} }
sendAsyncMessage("Marionette:listenersAttached", rv);
} }
} }
} }
@ -359,9 +361,9 @@ function sendLog(msg) {
/** /**
* Send error message to server * Send error message to server
*/ */
function sendError(message, status, trace, command_id) { function sendError(msg, code, stack, cmdId) {
let error_msg = { message: message, status: status, stacktrace: trace }; let payload = {message: msg, code: code, stack: stack};
sendToServer("Marionette:error", error_msg, command_id); sendToServer("Marionette:error", payload, cmdId);
} }
/** /**
@ -432,6 +434,8 @@ function createExecuteContentSandbox(aWindow, timeout) {
marionetteLogObj, timeout, marionetteLogObj, timeout,
heartbeatCallback, heartbeatCallback,
marionetteTestName); marionetteTestName);
marionette.runEmulatorCmd = (cmd, cb) => this.runEmulatorCmd(cmd, cb);
marionette.runEmulatorShell = (args, cb) => this.runEmulatorShell(args, cb);
sandbox.marionette = marionette; sandbox.marionette = marionette;
marionette.exports.forEach(function(fn) { marionette.exports.forEach(function(fn) {
try { try {
@ -1292,10 +1296,10 @@ function refresh(msg) {
function findElementContent(msg) { function findElementContent(msg) {
let command_id = msg.json.command_id; let command_id = msg.json.command_id;
try { try {
let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); }; let on_success = function(el, cmd_id) { sendResponse({value: el}, cmd_id) };
let on_error = sendError; let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
elementManager.find(curFrame, msg.json, msg.json.searchTimeout, elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
on_success, on_error, false, command_id); false /* all */, on_success, on_error, command_id);
} }
catch (e) { catch (e) {
sendError(e.message, e.code, e.stack, command_id); sendError(e.message, e.code, e.stack, command_id);
@ -1308,10 +1312,10 @@ function findElementContent(msg) {
function findElementsContent(msg) { function findElementsContent(msg) {
let command_id = msg.json.command_id; let command_id = msg.json.command_id;
try { try {
let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); }; let on_success = function(els, cmd_id) { sendResponse({value: els}, cmd_id); };
let on_error = sendError; let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
elementManager.find(curFrame, msg.json, msg.json.searchTimeout, elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
on_success, on_error, true, command_id); true /* all */, on_success, on_error, command_id);
} }
catch (e) { catch (e) {
sendError(e.message, e.code, e.stack, command_id); sendError(e.message, e.code, e.stack, command_id);
@ -1702,21 +1706,20 @@ function switchToFrame(msg) {
let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT']; let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue }); sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
if (curFrame.contentWindow == null) { let rv = null;
// The frame we want to switch to is a remote (out-of-process) frame; if (curFrame.contentWindow === null) {
// notify our parent to handle the switch. // The frame we want to switch to is a remote/OOP frame;
// notify our parent to handle the switch
curFrame = content; curFrame = content;
sendToServer('Marionette:switchToFrame', {win: parWindow, rv = {win: parWindow, frame: foundFrame};
frame: foundFrame, } else {
command_id: command_id});
}
else {
curFrame = curFrame.contentWindow; curFrame = curFrame.contentWindow;
if(msg.json.focus == true) { if (msg.json.focus)
curFrame.focus(); curFrame.focus();
}
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
} }
sendResponse({value: rv}, command_id);
} }
/** /**
* Add a cookie to the document * Add a cookie to the document

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,12 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file, * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = ["Marionette"];
/* /*
* The Marionette object, passed to the script context. * The Marionette object, passed to the script context.
*/ */
this.Marionette = function(scope, window, context, logObj, timeout,
this.Marionette = function Marionette(scope, window, context, logObj, timeout, heartbeatCallback, testName) {
heartbeatCallback, testName) {
this.scope = scope; this.scope = scope;
this.window = window; this.window = window;
this.tests = []; this.tests = [];
@ -19,12 +21,23 @@ this.Marionette = function Marionette(scope, window, context, logObj, timeout,
this.TEST_UNEXPECTED_PASS = "TEST-UNEXPECTED-PASS"; this.TEST_UNEXPECTED_PASS = "TEST-UNEXPECTED-PASS";
this.TEST_PASS = "TEST-PASS"; this.TEST_PASS = "TEST-PASS";
this.TEST_KNOWN_FAIL = "TEST-KNOWN-FAIL"; this.TEST_KNOWN_FAIL = "TEST-KNOWN-FAIL";
} };
Marionette.prototype = { Marionette.prototype = {
exports: ['ok', 'is', 'isnot', 'todo', 'log', 'getLogs', 'generate_results', 'waitFor', exports: [
'runEmulatorCmd', 'runEmulatorShell', 'TEST_PASS', 'TEST_KNOWN_FAIL', "ok",
'TEST_UNEXPECTED_FAIL', 'TEST_UNEXPECTED_PASS'], "is",
"isnot",
"todo",
"log",
"getLogs",
"generate_results",
"waitFor",
"TEST_PASS",
"TEST_KNOWN_FAIL",
"TEST_UNEXPECTED_FAIL",
"TEST_UNEXPECTED_PASS"
],
addTest: function Marionette__addTest(condition, name, passString, failString, diag, state) { addTest: function Marionette__addTest(condition, name, passString, failString, diag, state) {
@ -194,6 +207,4 @@ Marionette.prototype = {
this.heartbeatCallback(); this.heartbeatCallback();
this.scope.runEmulatorShell(args, callback); this.scope.runEmulatorShell(args, callback);
}, },
}; };

Просмотреть файл

@ -0,0 +1,171 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu} = Components;
const loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
const ServerSocket = CC("@mozilla.org/network/server-socket;1", "nsIServerSocket", "initSpecialConnection");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("chrome://marionette/content/dispatcher.js");
Cu.import("chrome://marionette/content/driver.js");
Cu.import("chrome://marionette/content/marionette-elements.js");
Cu.import("chrome://marionette/content/marionette-simpletest.js");
// Bug 1083711: Load transport.js as an SDK module instead of subscript
loader.loadSubScript("resource://gre/modules/devtools/transport/transport.js");
// Preserve this import order:
let events = {};
loader.loadSubScript("chrome://marionette/content/EventUtils.js", events);
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", events);
loader.loadSubScript("chrome://marionette/content/marionette-frame-manager.js");
const logger = Log.repository.getLogger("Marionette");
this.EXPORTED_SYMBOLS = ["MarionetteServer"];
const SPECIAL_POWERS_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
const CONTENT_LISTENER_PREF = "marionette.contentListener";
/**
* Bootstraps Marionette and handles incoming client connections.
*
* Once started, it opens a TCP socket sporting the debugger transport
* protocol on the provided port. For every new client a Dispatcher is
* created.
*
* @param {number} port
* Port for server to listen to.
* @param {boolean} forceLocal
* Listen only to connections from loopback if true. If false,
* accept all connections.
*/
this.MarionetteServer = function(port, forceLocal) {
this.port = port;
this.forceLocal = forceLocal;
this.conns = {};
this.nextConnId = 0;
this.alive = false;
};
/**
* Initialises the Marionette server by loading in the special powers
* which is required to provide some automation-only features.
*/
MarionetteServer.prototype.init = function() {
// SpecialPowers requires insecure automation-only features that we put behind a pref
Services.prefs.setBoolPref(SPECIAL_POWERS_PREF, true);
let specialpowers = {};
loader.loadSubScript(
"chrome://specialpowers/content/SpecialPowersObserver.js", specialpowers);
specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver();
specialpowers.specialPowersObserver.init();
};
/**
* Function that takes an Emulator and produces a GeckoDriver.
*
* Determines application name and device type to initialise the driver
* with. Also bypasses offline status if the device is a qemu or panda
* type device.
*
* @return {GeckoDriver}
* A driver instance.
*/
MarionetteServer.prototype.driverFactory = function(emulator) {
let appName = isMulet() ? "B2G" : Services.appinfo.name;
let qemu = "0";
let device = null;
let bypassOffline = false;
try {
Cu.import("resource://gre/modules/systemlibs.js");
qemu = libcutils.property_get("ro.kernel.qemu");
logger.debug("B2G emulator: " + (qemu == "1" ? "yes" : "no"));
device = libcutils.property_get("ro.product.device");
logger.debug("Device detected is " + device);
bypassOffline = (qemu == "1" || device == "panda");
} catch (e) {}
if (qemu == "1") {
device = "qemu";
}
if (!device) {
device = "desktop";
}
Services.prefs.setBoolPref(CONTENT_LISTENER_PREF, false);
if (bypassOffline) {
logger.debug("Bypassing offline status");
Services.prefs.setBoolPref("network.gonk.manage-offline-status", false);
Services.io.manageOfflineStatus = false;
Services.io.offline = false;
}
return new GeckoDriver(appName, device, emulator);
};
MarionetteServer.prototype.start = function() {
if (this.alive) {
return;
}
this.init();
let flags = Ci.nsIServerSocket.KeepWhenOffline;
if (this.forceLocal) {
flags |= Ci.nsIServerSocket.LoopbackOnly;
}
this.listener = new ServerSocket(this.port, flags, 0);
this.listener.asyncListen(this);
this.alive = true;
};
MarionetteServer.prototype.stop = function() {
if (!this.alive) {
return;
}
this.closeListener();
this.alive = false;
};
MarionetteServer.prototype.closeListener = function() {
this.listener.close();
this.listener = null;
};
MarionetteServer.prototype.onSocketAccepted = function(
serverSocket, clientSocket) {
let input = clientSocket.openInputStream(0, 0, 0);
let output = clientSocket.openOutputStream(0, 0, 0);
let transport = new DebuggerTransport(input, output);
let connId = "conn" + this.nextConnId++;
let dispatcher = new Dispatcher(connId, transport, this.driverFactory);
dispatcher.onclose = this.onConnectionClosed.bind(this);
this.conns[connId] = dispatcher;
logger.info(`Accepted connection ${connId} from ${clientSocket.host}:${clientSocket.port}`);
// Create a root actor for the connection and send the hello packet
dispatcher.sayHello();
transport.ready();
};
MarionetteServer.prototype.onConnectionClosed = function(conn) {
let id = conn.id;
delete this.conns[id];
logger.info(`Closed connection ${id}`);
};
function isMulet() {
try {
return Services.prefs.getBoolPref("b2g.is_mulet");
} catch (e) {
return false;
}
}