1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
10 Cu.import("resource://gre/modules/Log.jsm");
11 let logger = Log.repository.getLogger("Marionette");
12 logger.info('marionette-server.js loaded');
14 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
15 .getService(Ci.mozIJSSubScriptLoader);
16 loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
17 loader.loadSubScript("chrome://marionette/content/marionette-common.js");
18 Cu.import("resource://gre/modules/Services.jsm");
19 loader.loadSubScript("chrome://marionette/content/marionette-frame-manager.js");
20 Cu.import("chrome://marionette/content/marionette-elements.js");
22 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
23 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
24 loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
26 // SpecialPowers requires insecure automation-only features that we put behind a pref.
27 Services.prefs.setBoolPref('security.turn_off_all_security_so_that_viruses_can_take_over_this_computer',
29 let specialpowers = {};
30 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
32 specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver();
33 specialpowers.specialPowersObserver.init();
35 Cu.import("resource://gre/modules/FileUtils.jsm");
36 Cu.import("resource://gre/modules/NetUtil.jsm");
38 Services.prefs.setBoolPref("marionette.contentListener", false);
39 let appName = Services.appinfo.name;
41 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
42 let DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
43 this.DevToolsUtils = DevToolsUtils;
44 loader.loadSubScript("resource://gre/modules/devtools/transport/transport.js");
46 let bypassOffline = false;
51 XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
52 Cu.import("resource://gre/modules/systemlibs.js");
56 qemu = libcutils.property_get("ro.kernel.qemu");
57 logger.info("B2G emulator: " + (qemu == "1" ? "yes" : "no"));
58 device = libcutils.property_get("ro.product.device");
59 logger.info("Device detected is " + device);
60 bypassOffline = (qemu == "1" || device == "panda");
66 logger.info("Bypassing offline status.");
67 Services.prefs.setBoolPref("network.gonk.manage-offline-status", false);
68 Services.io.manageOfflineStatus = false;
69 Services.io.offline = false;
72 // This is used to prevent newSession from returning before the telephony
73 // API's are ready; see bug 792647. This assumes that marionette-server.js
74 // will be loaded before the 'system-message-listener-ready' message
75 // is fired. If this stops being true, this approach will have to change.
76 let systemMessageListenerReady = false;
77 Services.obs.addObserver(function() {
78 systemMessageListenerReady = true;
79 }, "system-message-listener-ready", false);
84 function FrameSendNotInitializedError(frame) {
87 this.message = "Error sending message to frame (NS_ERROR_NOT_INITIALIZED)";
88 this.toString = function() {
89 return this.message + " " + this.frame + "; frame has closed.";
93 function FrameSendFailureError(frame) {
96 this.message = "Error sending message to frame (NS_ERROR_FAILURE)";
97 this.toString = function() {
98 return this.message + " " + this.frame + "; frame not responding.";
103 * The server connection is responsible for all marionette API calls. It gets created
104 * for each connection and manages all chrome and browser based calls. It
105 * mediates content calls by issuing appropriate messages to the content process.
107 function MarionetteServerConnection(aPrefix, aTransport, aServer)
109 this.uuidGen = Cc["@mozilla.org/uuid-generator;1"]
110 .getService(Ci.nsIUUIDGenerator);
112 this.prefix = aPrefix;
113 this.server = aServer;
114 this.conn = aTransport;
115 this.conn.hooks = this;
117 // marionette uses a protocol based on the debugger server, which requires
118 // passing back "actor ids" with responses. unlike the debugger server,
119 // we don't have multiple actors, so just use a dummy value of "0" here
122 this.globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
123 .getService(Ci.nsIMessageBroadcaster);
124 this.messageManager = this.globalMessageManager;
125 this.browsers = {}; //holds list of BrowserObjs
126 this.curBrowser = null; // points to current browser
127 this.context = "content";
128 this.scriptTimeout = null;
129 this.searchTimeout = null;
130 this.pageTimeout = null;
132 this.inactivityTimer = null;
133 this.heartbeatCallback = function () {}; // called by simpletest methods
134 this.marionetteLog = new MarionetteLogObj();
135 this.command_id = null;
136 this.mainFrame = null; //topmost chrome frame
137 this.curFrame = null; // chrome iframe that currently has focus
138 this.mainContentFrameId = null;
139 this.importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']);
140 this.importedScriptHashes = {"chrome" : [], "content": []};
141 this.currentFrameElement = null;
142 this.testName = null;
143 this.mozBrowserClose = null;
144 this.oopFrameId = null; // frame ID of current remote frame, used for mozbrowserclose events
147 MarionetteServerConnection.prototype = {
149 QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
151 Ci.nsISupportsWeakReference]),
154 * Debugger transport callbacks:
156 onPacket: function MSC_onPacket(aPacket) {
157 // Dispatch the request
158 if (this.requestTypes && this.requestTypes[aPacket.name]) {
160 this.requestTypes[aPacket.name].bind(this)(aPacket);
162 this.conn.send({ error: ("error occurred while processing '" +
164 message: e.message });
167 this.conn.send({ error: "unrecognizedPacketType",
168 message: ('Marionette does not ' +
169 'recognize the packet type "' +
170 aPacket.name + '"') });
174 onClosed: function MSC_onClosed(aStatus) {
175 this.server._connectionClosed(this);
176 this.sessionTearDown();
184 * Switches to the global ChromeMessageBroadcaster, potentially replacing a frame-specific
185 * ChromeMessageSender. Has no effect if the global ChromeMessageBroadcaster is already
186 * in use. If this replaces a frame-specific ChromeMessageSender, it removes the message
187 * listeners from that sender, and then puts the corresponding frame script "to sleep",
188 * which removes most of the message listeners from it as well.
190 switchToGlobalMessageManager: function MDA_switchToGlobalMM() {
191 if (this.curBrowser && this.curBrowser.frameManager.currentRemoteFrame !== null) {
192 this.curBrowser.frameManager.removeMessageManagerListeners(this.messageManager);
193 this.sendAsync("sleepSession", null, null, true);
194 this.curBrowser.frameManager.currentRemoteFrame = null;
196 this.messageManager = this.globalMessageManager;
200 * Helper method to send async messages to the content listener
203 * Suffix of the targetted message listener (Marionette:<suffix>)
204 * @param object values
205 * Object to send to the listener
207 sendAsync: function MDA_sendAsync(name, values, commandId, ignoreFailure) {
210 values.command_id = commandId;
212 if (this.curBrowser.frameManager.currentRemoteFrame !== null) {
214 this.messageManager.sendAsyncMessage(
215 "Marionette:" + name + this.curBrowser.frameManager.currentRemoteFrame.targetFrameId, values);
218 if (!ignoreFailure) {
222 case Components.results.NS_ERROR_FAILURE:
223 error = new FrameSendFailureError(this.curBrowser.frameManager.currentRemoteFrame);
225 case Components.results.NS_ERROR_NOT_INITIALIZED:
226 error = new FrameSendNotInitializedError(this.curBrowser.frameManager.currentRemoteFrame);
231 let code = error.hasOwnProperty('code') ? e.code : 500;
232 this.sendError(error.toString(), code, error.stack, commandId);
237 this.messageManager.broadcastAsyncMessage(
238 "Marionette:" + name + this.curBrowser.curFrameId, values);
243 logRequest: function MDA_logRequest(type, data) {
244 logger.debug("Got request: " + type + ", data: " + JSON.stringify(data) + ", id: " + this.command_id);
248 * Generic method to pass a response to the client
251 * Response to send back to client
252 * @param string command_id
253 * Unique identifier assigned to the client's request.
254 * Used to distinguish the asynchronous responses.
256 sendToClient: function MDA_sendToClient(msg, command_id) {
257 logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id +
258 ", " + this.command_id);
260 logger.warn("got a response with no command_id");
263 else if (command_id != -1) {
264 // A command_id of -1 is used for emulator callbacks, and those
265 // don't use this.command_id.
266 if (!this.command_id) {
267 // A null value for this.command_id means we've already processed
268 // a message for the previous value, and so the current message is a
270 logger.warn("ignoring duplicate response for command_id " + command_id);
273 else if (this.command_id != command_id) {
274 logger.warn("ignoring out-of-sync response");
279 if (command_id != -1) {
280 // Don't unset this.command_id if this message is to process an
281 // emulator callback, since another response for this command_id is
282 // expected, after the containing call to execute_async_script finishes.
283 this.command_id = null;
288 * Send a value to client
290 * @param object value
291 * Value to send back to client
292 * @param string command_id
293 * Unique identifier assigned to the client's request.
294 * Used to distinguish the asynchronous responses.
296 sendResponse: function MDA_sendResponse(value, command_id) {
297 if (typeof(value) == 'undefined')
299 this.sendToClient({from:this.actorID, value: value}, command_id);
302 sayHello: function MDA_sayHello() {
303 this.conn.send({ from: "root",
304 applicationType: "gecko",
308 getMarionetteID: function MDA_getMarionette() {
309 this.conn.send({ "from": "root", "id": this.actorID });
315 * @param string command_id
316 * Unique identifier assigned to the client's request.
317 * Used to distinguish the asynchronous responses.
319 sendOk: function MDA_sendOk(command_id) {
320 this.sendToClient({from:this.actorID, ok: true}, command_id);
324 * Send error message to client
326 * @param string message
328 * @param number status
330 * @param string trace
332 * @param string command_id
333 * Unique identifier assigned to the client's request.
334 * Used to distinguish the asynchronous responses.
336 sendError: function MDA_sendError(message, status, trace, command_id) {
337 let error_msg = {message: message, status: status, stacktrace: trace};
338 this.sendToClient({from:this.actorID, error: error_msg}, command_id);
342 * Gets the current active window
344 * @return nsIDOMWindow
346 getCurrentWindow: function MDA_getCurrentWindow() {
348 if (this.curFrame == null) {
349 if (this.curBrowser == null) {
350 if (this.context == "content") {
351 type = 'navigator:browser';
353 return Services.wm.getMostRecentWindow(type);
356 return this.curBrowser.window;
360 return this.curFrame;
365 * Gets the the window enumerator
367 * @return nsISimpleEnumerator
369 getWinEnumerator: function MDA_getWinEnumerator() {
371 if (appName != "B2G" && this.context == "content") {
372 type = 'navigator:browser';
374 return Services.wm.getEnumerator(type);
378 * Create a new BrowserObj for window and add to known browsers
380 * @param nsIDOMWindow win
381 * Window for which we will create a BrowserObj
384 * Returns the unique server-assigned ID of the window
386 addBrowser: function MDA_addBrowser(win) {
387 let browser = new BrowserObj(win, this);
388 let winId = win.QueryInterface(Ci.nsIInterfaceRequestor).
389 getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
390 winId = winId + ((appName == "B2G") ? '-b2g' : '');
391 this.browsers[winId] = browser;
392 this.curBrowser = this.browsers[winId];
393 if (this.curBrowser.elementManager.seenItems[winId] == undefined) {
394 //add this to seenItems so we can guarantee the user will get winId as this window's id
395 this.curBrowser.elementManager.seenItems[winId] = Cu.getWeakReference(win);
400 * Start a new session in a new browser.
402 * If newSession is true, we will switch focus to the start frame
403 * when it registers. Also, if it is in desktop, then a new tab
404 * with the start page uri (about:blank) will be opened.
406 * @param nsIDOMWindow win
407 * Window whose browser we need to access
408 * @param boolean newSession
409 * True if this is the first time we're talking to this browser
411 startBrowser: function MDA_startBrowser(win, newSession) {
412 this.mainFrame = win;
413 this.curFrame = null;
414 this.addBrowser(win);
415 this.curBrowser.newSession = newSession;
416 this.curBrowser.startSession(newSession, win, this.whenBrowserStarted.bind(this));
420 * Callback invoked after a new session has been started in a browser.
421 * Loads the Marionette frame script into the browser if needed.
423 * @param nsIDOMWindow win
424 * Window whose browser we need to access
425 * @param boolean newSession
426 * True if this is the first time we're talking to this browser
428 whenBrowserStarted: function MDA_whenBrowserStarted(win, newSession) {
430 if (!Services.prefs.getBoolPref("marionette.contentListener") || !newSession) {
431 this.curBrowser.loadFrameScript(FRAME_SCRIPT, win);
435 //there may not always be a content process
436 logger.info("could not load listener into content for page: " + win.location.href);
442 * Recursively get all labeled text
444 * @param nsIDOMElement el
447 * Array that holds the text lines
449 getVisibleText: function MDA_getVisibleText(el, lines) {
450 let nodeName = el.nodeName;
452 if (utils.isElementDisplayed(el)) {
454 lines.push(el.value);
456 for (var child in el.childNodes) {
457 this.getVisibleText(el.childNodes[child], lines);
462 if (nodeName == "#text") {
463 lines.push(el.textContent);
468 getCommandId: function MDA_getCommandId() {
469 return this.uuidGen.generateUUID().toString();
473 * Given a file name, this will delete the file from the temp directory if it exists
475 deleteFile: function(filename) {
476 let file = FileUtils.getFile('TmpD', [filename.toString()]);
485 * All methods implementing a command from the client should create a
486 * command_id, and then use this command_id in all messages exchanged with
487 * the frame scripts and with responses sent to the client. This prevents
488 * commands and responses from getting out-of-sync, which can happen in
489 * the case of execute_async calls that timeout and then later send a
490 * response, and other situations. See bug 779011. See setScriptTimeout()
491 * for a basic example.
495 * Create a new session. This creates a new BrowserObj.
497 * In a desktop environment, this opens a new browser with
498 * "about:blank" which subsequent commands will be sent to.
500 * This will send a hash map of supported capabilities to the client
501 * as part of the Marionette:register IPC command in the
502 * receiveMessage callback when a new browser is created.
504 newSession: function MDA_newSession() {
505 this.command_id = this.getCommandId();
506 this.newSessionCommandId = this.command_id;
508 this.scriptTimeout = 10000;
510 function waitForWindow() {
511 let win = this.getCurrentWindow();
513 // If the window isn't even created, just poll wait for it
514 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
515 checkTimer.initWithCallback(waitForWindow.bind(this), 100,
516 Ci.nsITimer.TYPE_ONE_SHOT);
518 else if (win.document.readyState != "complete") {
519 // Otherwise, wait for it to be fully loaded before proceeding
520 let listener = (evt) => {
521 // ensure that we proceed, on the top level document load event
522 // (not an iframe one...)
523 if (evt.target != win.document) {
526 win.removeEventListener("load", listener);
527 waitForWindow.call(this);
529 win.addEventListener("load", listener, true);
532 this.startBrowser(win, true);
536 if (!Services.prefs.getBoolPref("marionette.contentListener")) {
537 waitForWindow.call(this);
539 else if ((appName != "Firefox") && (this.curBrowser == null)) {
540 // If there is a content listener, then we just wake it up
541 this.addBrowser(this.getCurrentWindow());
542 this.curBrowser.startSession(false, this.getCurrentWindow(),
543 this.whenBrowserStarted);
544 this.messageManager.broadcastAsyncMessage("Marionette:restart", {});
547 this.sendError("Session already running", 500, null,
550 this.switchToGlobalMessageManager();
554 * Send the current session's capabilities to the client.
556 * Capabilities informs the client of which WebDriver features are
557 * supported by Firefox and Marionette. They are immutable for the
558 * length of the session.
560 * The return value is an immutable map of string keys
561 * ("capabilities") to values, which may be of types boolean,
562 * numerical or string.
564 getSessionCapabilities: function MDA_getSessionCapabilities() {
565 this.command_id = this.getCommandId();
567 let isB2G = appName == "B2G";
568 let platformName = Services.appinfo.OS.toUpperCase();
571 // Mandated capabilities
572 "browserName": appName,
573 "browserVersion": Services.appinfo.version,
574 "platformName": platformName,
575 "platformVersion": Services.appinfo.platformVersion,
577 // Supported features
578 "handlesAlerts": false,
579 "nativeEvents": false,
582 "takesElementScreenshot": true,
583 "takesScreenshot": true,
586 "platform": platformName,
588 // Proprietary extensions
589 "XULappId" : Services.appinfo.ID,
590 "appBuildId" : Services.appinfo.appBuildID,
591 "device": qemu == "1" ? "qemu" : (!device ? "desktop" : device),
592 "version": Services.appinfo.version
595 // eideticker (bug 965297) and mochitest (bug 965304)
596 // compatibility. They only check for the presence of this
597 // property and should so not be in caps if not on a B2G device.
601 this.sendResponse(caps, this.command_id);
605 * Log message. Accepts user defined log-level.
607 * @param object aRequest
608 * 'value' member holds log message
609 * 'level' member hold log level
611 log: function MDA_log(aRequest) {
612 this.command_id = this.getCommandId();
613 this.marionetteLog.log(aRequest.parameters.value, aRequest.parameters.level);
614 this.sendOk(this.command_id);
618 * Return all logged messages.
620 getLogs: function MDA_getLogs() {
621 this.command_id = this.getCommandId();
622 this.sendResponse(this.marionetteLog.getLogs(), this.command_id);
626 * Sets the context of the subsequent commands to be either 'chrome' or 'content'
628 * @param object aRequest
629 * 'value' member holds the name of the context to be switched to
631 setContext: function MDA_setContext(aRequest) {
632 this.command_id = this.getCommandId();
633 this.logRequest("setContext", aRequest);
634 let context = aRequest.parameters.value;
635 if (context != "content" && context != "chrome") {
636 this.sendError("invalid context", 500, null, this.command_id);
639 this.context = context;
640 this.sendOk(this.command_id);
645 * Returns a chrome sandbox that can be used by the execute_foo functions.
647 * @param nsIDOMWindow aWindow
648 * Window in which we will execute code
649 * @param Marionette marionette
650 * Marionette test instance
654 * Returns the sandbox
656 createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args, specialPowers, command_id) {
658 args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow);
661 this.sendError(e.message, e.code, e.stack, command_id);
665 let _chromeSandbox = new Cu.Sandbox(aWindow,
666 { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''});
667 _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
668 _chromeSandbox.__marionetteParams = args;
669 _chromeSandbox.testUtils = utils;
671 marionette.exports.forEach(function(fn) {
673 _chromeSandbox[fn] = marionette[fn].bind(marionette);
676 _chromeSandbox[fn] = marionette[fn];
680 _chromeSandbox.isSystemMessageListenerReady =
681 function() { return systemMessageListenerReady; }
683 if (specialPowers == true) {
684 loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js",
686 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js",
688 loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js",
692 return _chromeSandbox;
696 * Executes a script in the given sandbox.
698 * @param Sandbox sandbox
699 * Sandbox in which the script will run
700 * @param string script
702 * @param boolean directInject
703 * If true, then the script will be run as is,
704 * and not as a function body (as you would
705 * do using the WebDriver spec)
706 * @param boolean async
707 * True if the script is asynchronous
709 executeScriptInSandbox: function MDA_executeScriptInSandbox(sandbox, script,
710 directInject, async, command_id, timeout) {
712 if (directInject && async &&
713 (timeout == null || timeout == 0)) {
714 this.sendError("Please set a timeout", 21, null, command_id);
718 if (this.importedScripts.exists()) {
719 let stream = Cc["@mozilla.org/network/file-input-stream;1"].
720 createInstance(Ci.nsIFileInputStream);
721 stream.init(this.importedScripts, -1, 0, 0);
722 let data = NetUtil.readInputStreamToString(stream, stream.available());
724 script = data + script;
727 let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
729 if (directInject && !async &&
730 (res == undefined || res.passed == undefined)) {
731 this.sendError("finish() not called", 500, null, command_id);
736 this.sendResponse(this.curBrowser.elementManager.wrapValue(res),
742 * Execute the given script either as a function body (executeScript)
743 * or directly (for 'mochitest' like JS Marionette tests)
745 * @param object aRequest
746 * 'script' member is the script to run
747 * 'args' member holds the arguments to the script
748 * @param boolean directInject
749 * if true, it will be run directly and not as a
752 execute: function MDA_execute(aRequest, directInject) {
753 let inactivityTimeout = aRequest.parameters.inactivityTimeout;
754 let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
755 let command_id = this.command_id = this.getCommandId();
757 this.logRequest("execute", aRequest);
758 if (aRequest.parameters.newSandbox == undefined) {
759 //if client does not send a value in newSandbox,
760 //then they expect the same behaviour as webdriver
761 aRequest.parameters.newSandbox = true;
763 if (this.context == "content") {
764 this.sendAsync("executeScript",
766 script: aRequest.parameters.script,
767 args: aRequest.parameters.args,
768 newSandbox: aRequest.parameters.newSandbox,
770 specialPowers: aRequest.parameters.specialPowers,
771 filename: aRequest.parameters.filename,
772 line: aRequest.parameters.line
778 // handle the inactivity timeout
780 if (inactivityTimeout) {
781 let inactivityTimeoutHandler = function(message, status) {
782 let error_msg = {message: value, status: status};
783 that.sendToClient({from: that.actorID, error: error_msg},
784 marionette.command_id);
786 let setTimer = function() {
787 that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
788 if (that.inactivityTimer != null) {
789 that.inactivityTimer.initWithCallback(function() {
790 inactivityTimeoutHandler("timed out due to inactivity", 28);
791 }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT);
795 this.heartbeatCallback = function resetInactivityTimer() {
796 that.inactivityTimer.cancel();
801 let curWindow = this.getCurrentWindow();
802 let marionette = new Marionette(this, curWindow, "chrome",
804 timeout, this.heartbeatCallback, this.testName);
805 let _chromeSandbox = this.createExecuteSandbox(curWindow,
807 aRequest.parameters.args,
808 aRequest.parameters.specialPowers,
814 _chromeSandbox.finish = function chromeSandbox_finish() {
815 if (that.inactivityTimer != null) {
816 that.inactivityTimer.cancel();
818 return marionette.generate_results();
822 script = aRequest.parameters.script;
825 script = "let func = function() {" +
826 aRequest.parameters.script +
828 "func.apply(null, __marionetteParams);";
830 this.executeScriptInSandbox(_chromeSandbox, script, directInject,
831 false, command_id, timeout);
834 let error = createStackMessage(e,
836 aRequest.parameters.filename,
837 aRequest.parameters.line,
839 this.sendError(error[0], 17, error[1], command_id);
844 * Set the timeout for asynchronous script execution
846 * @param object aRequest
847 * 'ms' member is time in milliseconds to set timeout
849 setScriptTimeout: function MDA_setScriptTimeout(aRequest) {
850 this.command_id = this.getCommandId();
851 let timeout = parseInt(aRequest.parameters.ms);
853 this.sendError("Not a Number", 500, null, this.command_id);
856 this.scriptTimeout = timeout;
857 this.sendOk(this.command_id);
862 * execute pure JS script. Used to execute 'mochitest'-style Marionette tests.
864 * @param object aRequest
865 * 'script' member holds the script to execute
866 * 'args' member holds the arguments to the script
867 * 'timeout' member will be used as the script timeout if it is given
869 executeJSScript: function MDA_executeJSScript(aRequest) {
870 let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
871 let command_id = this.command_id = this.getCommandId();
873 //all pure JS scripts will need to call Marionette.finish() to complete the test.
874 if (aRequest.newSandbox == undefined) {
875 //if client does not send a value in newSandbox,
876 //then they expect the same behaviour as webdriver
877 aRequest.newSandbox = true;
879 if (this.context == "chrome") {
880 if (aRequest.parameters.async) {
881 this.executeWithCallback(aRequest, aRequest.parameters.async);
884 this.execute(aRequest, true);
888 this.sendAsync("executeJSScript",
890 script: aRequest.parameters.script,
891 args: aRequest.parameters.args,
892 newSandbox: aRequest.parameters.newSandbox,
893 async: aRequest.parameters.async,
895 inactivityTimeout: aRequest.parameters.inactivityTimeout,
896 specialPowers: aRequest.parameters.specialPowers,
897 filename: aRequest.parameters.filename,
898 line: aRequest.parameters.line,
905 * This function is used by executeAsync and executeJSScript to execute a script
908 * For executeJSScript, it will return a message only when the finish() method is called.
909 * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
910 * method is called, or if it times out.
912 * @param object aRequest
913 * 'script' member holds the script to execute
914 * 'args' member holds the arguments for the script
915 * @param boolean directInject
916 * if true, it will be run directly and not as a
919 executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
920 let inactivityTimeout = aRequest.parameters.inactivityTimeout;
921 let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
922 let command_id = this.command_id = this.getCommandId();
924 this.logRequest("executeWithCallback", aRequest);
925 if (aRequest.parameters.newSandbox == undefined) {
926 //if client does not send a value in newSandbox,
927 //then they expect the same behaviour as webdriver
928 aRequest.parameters.newSandbox = true;
931 if (this.context == "content") {
932 this.sendAsync("executeAsyncScript",
934 script: aRequest.parameters.script,
935 args: aRequest.parameters.args,
937 newSandbox: aRequest.parameters.newSandbox,
939 inactivityTimeout: inactivityTimeout,
940 specialPowers: aRequest.parameters.specialPowers,
941 filename: aRequest.parameters.filename,
942 line: aRequest.parameters.line
948 // handle the inactivity timeout
950 if (inactivityTimeout) {
951 this.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
952 if (this.inactivityTimer != null) {
953 this.inactivityTimer.initWithCallback(function() {
954 chromeAsyncReturnFunc("timed out due to inactivity", 28);
955 }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT);
957 this.heartbeatCallback = function resetInactivityTimer() {
958 that.inactivityTimer.cancel();
959 that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
960 if (that.inactivityTimer != null) {
961 that.inactivityTimer.initWithCallback(function() {
962 chromeAsyncReturnFunc("timed out due to inactivity", 28);
963 }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT);
968 let curWindow = this.getCurrentWindow();
969 let original_onerror = curWindow.onerror;
971 that.timeout = timeout;
972 let marionette = new Marionette(this, curWindow, "chrome",
974 timeout, this.heartbeatCallback, this.testName);
975 marionette.command_id = this.command_id;
977 function chromeAsyncReturnFunc(value, status, stacktrace) {
978 if (that._emu_cbs && Object.keys(that._emu_cbs).length) {
979 value = "Emulator callback still pending when finish() called";
981 that._emu_cbs = null;
984 if (value == undefined)
986 if (that.command_id == marionette.command_id) {
987 if (that.timer != null) {
992 curWindow.onerror = original_onerror;
994 if (status == 0 || status == undefined) {
995 that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status},
996 marionette.command_id);
999 let error_msg = {message: value, status: status, stacktrace: stacktrace};
1000 that.sendToClient({from: that.actorID, error: error_msg},
1001 marionette.command_id);
1004 if (that.inactivityTimer != null) {
1005 that.inactivityTimer.cancel();
1009 curWindow.onerror = function (errorMsg, url, lineNumber) {
1010 chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17);
1014 function chromeAsyncFinish() {
1015 chromeAsyncReturnFunc(marionette.generate_results(), 0);
1018 let _chromeSandbox = this.createExecuteSandbox(curWindow,
1020 aRequest.parameters.args,
1021 aRequest.parameters.specialPowers,
1023 if (!_chromeSandbox)
1028 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1029 if (this.timer != null) {
1030 this.timer.initWithCallback(function() {
1031 chromeAsyncReturnFunc("timed out", 28);
1032 }, that.timeout, Ci.nsITimer.TYPE_ONESHOT);
1035 _chromeSandbox.returnFunc = chromeAsyncReturnFunc;
1036 _chromeSandbox.finish = chromeAsyncFinish;
1039 script = aRequest.parameters.script;
1042 script = '__marionetteParams.push(returnFunc);'
1043 + 'let marionetteScriptFinished = returnFunc;'
1044 + 'let __marionetteFunc = function() {' + aRequest.parameters.script + '};'
1045 + '__marionetteFunc.apply(null, __marionetteParams);';
1048 this.executeScriptInSandbox(_chromeSandbox, script, directInject,
1049 true, command_id, timeout);
1051 let error = createStackMessage(e,
1052 "execute_async_script",
1053 aRequest.parameters.filename,
1054 aRequest.parameters.line,
1056 chromeAsyncReturnFunc(error[0], 17, error[1]);
1061 * Navigate to to given URL.
1063 * This will follow redirects issued by the server. When the method
1064 * returns is based on the page load strategy that the user has
1067 * Documents that contain a META tag with the "http-equiv" attribute
1068 * set to "refresh" will return if the timeout is greater than 1
1069 * second and the other criteria for determining whether a page is
1070 * loaded are met. When the refresh period is 1 second or less and
1071 * the page load strategy is "normal" or "conservative", it will
1072 * wait for the page to complete loading before returning.
1074 * If any modal dialog box, such as those opened on
1075 * window.onbeforeunload or window.alert, is opened at any point in
1076 * the page load, it will return immediately.
1078 * If a 401 response is seen by the browser, it will return
1079 * immediately. That is, if BASIC, DIGEST, NTLM or similar
1080 * authentication is required, the page load is assumed to be
1081 * complete. This does not include FORM-based authentication.
1083 * @param object aRequest where <code>url</code> property holds the
1084 * URL to navigate to
1086 get: function MDA_get(aRequest) {
1087 let command_id = this.command_id = this.getCommandId();
1088 if (this.context != "chrome") {
1089 aRequest.command_id = command_id;
1090 aRequest.parameters.pageTimeout = this.pageTimeout;
1091 this.sendAsync("get", aRequest.parameters, command_id);
1095 this.getCurrentWindow().location.href = aRequest.parameters.url;
1096 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1097 let start = new Date().getTime();
1100 function checkLoad() {
1101 end = new Date().getTime();
1102 let elapse = end - start;
1103 if (this.pageTimeout == null || elapse <= this.pageTimeout){
1104 if (curWindow.document.readyState == "complete") {
1109 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1113 sendError("Error loading page", 13, null, command_id);
1117 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1121 * Get a string representing the current URL.
1123 * On Desktop this returns a string representation of the URL of the
1124 * current top level browsing context. This is equivalent to
1125 * document.location.href.
1127 * When in the context of the chrome, this returns the canonical URL
1128 * of the current resource.
1130 getCurrentUrl: function MDA_getCurrentUrl() {
1131 this.command_id = this.getCommandId();
1132 if (this.context == "chrome") {
1133 this.sendResponse(this.getCurrentWindow().location.href, this.command_id);
1136 this.sendAsync("getCurrentUrl", {}, this.command_id);
1141 * Gets the current title of the window
1143 getTitle: function MDA_getTitle() {
1144 this.command_id = this.getCommandId();
1145 if (this.context == "chrome"){
1146 var curWindow = this.getCurrentWindow();
1147 var title = curWindow.document.documentElement.getAttribute('title');
1148 this.sendResponse(title, this.command_id);
1151 this.sendAsync("getTitle", {}, this.command_id);
1156 * Gets the current type of the window
1158 getWindowType: function MDA_getWindowType() {
1159 this.command_id = this.getCommandId();
1160 var curWindow = this.getCurrentWindow();
1161 var type = curWindow.document.documentElement.getAttribute('windowtype');
1162 this.sendResponse(type, this.command_id);
1166 * Gets the page source of the content document
1168 getPageSource: function MDA_getPageSource(){
1169 this.command_id = this.getCommandId();
1170 if (this.context == "chrome"){
1171 let curWindow = this.getCurrentWindow();
1172 let XMLSerializer = curWindow.XMLSerializer;
1173 let pageSource = new XMLSerializer().serializeToString(curWindow.document);
1174 this.sendResponse(pageSource, this.command_id);
1177 this.sendAsync("getPageSource", {}, this.command_id);
1182 * Go back in history
1184 goBack: function MDA_goBack() {
1185 this.command_id = this.getCommandId();
1186 this.sendAsync("goBack", {}, this.command_id);
1190 * Go forward in history
1192 goForward: function MDA_goForward() {
1193 this.command_id = this.getCommandId();
1194 this.sendAsync("goForward", {}, this.command_id);
1200 refresh: function MDA_refresh() {
1201 this.command_id = this.getCommandId();
1202 this.sendAsync("refresh", {}, this.command_id);
1206 * Get the current window's handle.
1208 * Return an opaque server-assigned identifier to this window that
1209 * uniquely identifies it within this Marionette instance. This can
1210 * be used to switch to this window at a later point.
1212 * @return unique window handle (string)
1214 getWindowHandle: function MDA_getWindowHandle() {
1215 this.command_id = this.getCommandId();
1216 for (let i in this.browsers) {
1217 if (this.curBrowser == this.browsers[i]) {
1218 this.sendResponse(i, this.command_id);
1225 * Get list of windows in the current context.
1227 * If called in the content context it will return a list of
1228 * references to all available browser windows. Called in the
1229 * chrome context, it will list all available windows, not just
1230 * browser windows (e.g. not just navigator.browser).
1232 * Each window handle is assigned by the server, and the array of
1233 * strings returned does not have a guaranteed ordering.
1235 * @return unordered array of unique window handles as strings
1237 getWindowHandles: function MDA_getWindowHandles() {
1238 this.command_id = this.getCommandId();
1240 let winEn = this.getWinEnumerator();
1241 while (winEn.hasMoreElements()) {
1242 let foundWin = winEn.getNext();
1243 let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor)
1244 .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
1245 winId = winId + ((appName == "B2G") ? "-b2g" : "");
1248 this.sendResponse(res, this.command_id);
1252 * Switch to a window based on name or server-assigned id.
1253 * Searches based on name, then id.
1255 * @param object aRequest
1256 * 'name' member holds the name or id of the window to switch to
1258 switchToWindow: function MDA_switchToWindow(aRequest) {
1259 let command_id = this.command_id = this.getCommandId();
1260 let winEn = this.getWinEnumerator();
1261 while(winEn.hasMoreElements()) {
1262 let foundWin = winEn.getNext();
1263 let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor)
1264 .getInterface(Ci.nsIDOMWindowUtils)
1266 winId = winId + ((appName == "B2G") ? '-b2g' : '');
1267 if (aRequest.parameters.name == foundWin.name || aRequest.parameters.name == winId) {
1268 if (this.browsers[winId] == undefined) {
1269 //enable Marionette in that browser window
1270 this.startBrowser(foundWin, false);
1273 utils.window = foundWin;
1274 this.curBrowser = this.browsers[winId];
1276 this.sendOk(command_id);
1280 this.sendError("Unable to locate window " + aRequest.parameters.name, 23, null,
1284 getActiveFrame: function MDA_getActiveFrame() {
1285 this.command_id = this.getCommandId();
1287 if (this.context == "chrome") {
1288 if (this.curFrame) {
1289 let frameUid = this.curBrowser.elementManager.addToKnownElements(this.curFrame.frameElement);
1290 this.sendResponse(frameUid, this.command_id);
1292 // no current frame, we're at toplevel
1293 this.sendResponse(null, this.command_id);
1297 this.sendResponse(this.currentFrameElement, this.command_id);
1302 * Switch to a given frame within the current window
1304 * @param object aRequest
1305 * 'element' is the element to switch to
1306 * 'id' if element is not set, then this
1307 * holds either the id, name or index
1308 * of the frame to switch to
1310 switchToFrame: function MDA_switchToFrame(aRequest) {
1311 let command_id = this.command_id = this.getCommandId();
1312 this.logRequest("switchToFrame", aRequest);
1313 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1314 let curWindow = this.getCurrentWindow();
1315 let checkLoad = function() {
1316 let errorRegex = /about:.+(error)|(blocked)\?/;
1317 let curWindow = this.getCurrentWindow();
1318 if (curWindow.document.readyState == "complete") {
1319 this.sendOk(command_id);
1322 else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)) {
1323 this.sendError("Error loading page", 13, null, command_id);
1327 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1329 if (this.context == "chrome") {
1330 let foundFrame = null;
1331 if ((aRequest.parameters.id == null) && (aRequest.parameters.element == null)) {
1332 this.curFrame = null;
1333 if (aRequest.parameters.focus) {
1334 this.mainFrame.focus();
1336 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1339 if (aRequest.parameters.element != undefined) {
1340 if (this.curBrowser.elementManager.seenItems[aRequest.parameters.element]) {
1341 let wantedFrame = this.curBrowser.elementManager.getKnownElement(aRequest.parameters.element, curWindow); //HTMLIFrameElement
1342 // Deal with an embedded xul:browser case
1343 if (wantedFrame.tagName == "xul:browser") {
1344 curWindow = wantedFrame.contentWindow;
1345 this.curFrame = curWindow;
1346 if (aRequest.parameters.focus) {
1347 this.curFrame.focus();
1349 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1352 // else, assume iframe
1353 let frames = curWindow.document.getElementsByTagName("iframe");
1354 let numFrames = frames.length;
1355 for (let i = 0; i < numFrames; i++) {
1356 if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) {
1357 curWindow = frames[i].contentWindow;
1358 this.curFrame = curWindow;
1359 if (aRequest.parameters.focus) {
1360 this.curFrame.focus();
1362 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1368 switch(typeof(aRequest.parameters.id)) {
1370 let foundById = null;
1371 let frames = curWindow.document.getElementsByTagName("iframe");
1372 let numFrames = frames.length;
1373 for (let i = 0; i < numFrames; i++) {
1374 //give precedence to name
1375 let frame = frames[i];
1376 if (frame.getAttribute("name") == aRequest.parameters.id) {
1378 curWindow = frame.contentWindow;
1380 } else if ((foundById == null) && (frame.id == aRequest.parameters.id)) {
1384 if ((foundFrame == null) && (foundById != null)) {
1385 foundFrame = foundById;
1386 curWindow = frames[foundById].contentWindow;
1390 if (curWindow.frames[aRequest.parameters.id] != undefined) {
1391 foundFrame = aRequest.parameters.id;
1392 curWindow = curWindow.frames[foundFrame].frameElement.contentWindow;
1396 if (foundFrame != null) {
1397 this.curFrame = curWindow;
1398 if (aRequest.parameters.focus) {
1399 this.curFrame.focus();
1401 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1403 this.sendError("Unable to locate frame: " + aRequest.parameters.id, 8, null,
1408 if ((!aRequest.parameters.id) && (!aRequest.parameters.element) &&
1409 (this.curBrowser.frameManager.currentRemoteFrame !== null)) {
1410 // We're currently using a ChromeMessageSender for a remote frame, so this
1411 // request indicates we need to switch back to the top-level (parent) frame.
1412 // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so
1413 // we send the message to the right listener.
1414 this.switchToGlobalMessageManager();
1416 aRequest.command_id = command_id;
1417 this.sendAsync("switchToFrame", aRequest.parameters, command_id);
1422 * Set timeout for searching for elements
1424 * @param object aRequest
1425 * 'ms' holds the search timeout in milliseconds
1427 setSearchTimeout: function MDA_setSearchTimeout(aRequest) {
1428 this.command_id = this.getCommandId();
1429 let timeout = parseInt(aRequest.parameters.ms);
1430 if (isNaN(timeout)) {
1431 this.sendError("Not a Number", 500, null, this.command_id);
1434 this.searchTimeout = timeout;
1435 this.sendOk(this.command_id);
1440 * Set timeout for page loading, searching and scripts
1442 * @param object aRequest
1443 * 'type' hold the type of timeout
1444 * 'ms' holds the timeout in milliseconds
1446 timeouts: function MDA_timeouts(aRequest){
1448 this.command_id = this.getCommandId();
1449 let timeout_type = aRequest.parameters.type;
1450 let timeout = parseInt(aRequest.parameters.ms);
1451 if (isNaN(timeout)) {
1452 this.sendError("Not a Number", 500, null, this.command_id);
1455 if (timeout_type == "implicit") {
1456 this.setSearchTimeout(aRequest);
1458 else if (timeout_type == "script") {
1459 this.setScriptTimeout(aRequest);
1462 this.pageTimeout = timeout;
1463 this.sendOk(this.command_id);
1471 * @param object aRequest
1472 'element' represents the ID of the element to single tap on
1474 singleTap: function MDA_singleTap(aRequest) {
1475 this.command_id = this.getCommandId();
1476 let serId = aRequest.parameters.id;
1477 let x = aRequest.parameters.x;
1478 let y = aRequest.parameters.y;
1479 if (this.context == "chrome") {
1480 this.sendError("Command 'singleTap' is not available in chrome context", 500, null, this.command_id);
1483 this.sendAsync("singleTap",
1496 * @param object aRequest
1497 * 'value' represents a nested array: inner array represents each event; outer array represents collection of events
1499 actionChain: function MDA_actionChain(aRequest) {
1500 this.command_id = this.getCommandId();
1501 if (this.context == "chrome") {
1502 this.sendError("Command 'actionChain' is not available in chrome context", 500, null, this.command_id);
1505 this.sendAsync("actionChain",
1507 chain: aRequest.parameters.chain,
1508 nextId: aRequest.parameters.nextId
1517 * @param object aRequest
1518 * 'value' represents a nested array: inner array represents each event;
1519 * middle array represents collection of events for each finger
1520 * outer array represents all the fingers
1523 multiAction: function MDA_multiAction(aRequest) {
1524 this.command_id = this.getCommandId();
1525 if (this.context == "chrome") {
1526 this.sendError("Command 'multiAction' is not available in chrome context", 500, null, this.command_id);
1529 this.sendAsync("multiAction",
1531 value: aRequest.parameters.value,
1532 maxlen: aRequest.parameters.max_length
1539 * Find an element using the indicated search strategy.
1541 * @param object aRequest
1542 * 'using' member indicates which search method to use
1543 * 'value' member is the value the client is looking for
1545 findElement: function MDA_findElement(aRequest) {
1546 let command_id = this.command_id = this.getCommandId();
1547 if (this.context == "chrome") {
1550 let on_success = this.sendResponse.bind(this);
1551 let on_error = this.sendError.bind(this);
1552 id = this.curBrowser.elementManager.find(
1553 this.getCurrentWindow(),
1554 aRequest.parameters,
1562 this.sendError(e.message, e.code, e.stack, command_id);
1567 this.sendAsync("findElementContent",
1569 value: aRequest.parameters.value,
1570 using: aRequest.parameters.using,
1571 element: aRequest.parameters.element,
1572 searchTimeout: this.searchTimeout
1579 * Find elements using the indicated search strategy.
1581 * @param object aRequest
1582 * 'using' member indicates which search method to use
1583 * 'value' member is the value the client is looking for
1585 findElements: function MDA_findElements(aRequest) {
1586 let command_id = this.command_id = this.getCommandId();
1587 if (this.context == "chrome") {
1590 let on_success = this.sendResponse.bind(this);
1591 let on_error = this.sendError.bind(this);
1592 id = this.curBrowser.elementManager.find(this.getCurrentWindow(),
1593 aRequest.parameters,
1601 this.sendError(e.message, e.code, e.stack, command_id);
1606 this.sendAsync("findElementsContent",
1608 value: aRequest.parameters.value,
1609 using: aRequest.parameters.using,
1610 element: aRequest.parameters.element,
1611 searchTimeout: this.searchTimeout
1618 * Return the active element on the page
1620 getActiveElement: function MDA_getActiveElement(){
1621 let command_id = this.command_id = this.getCommandId();
1622 this.sendAsync("getActiveElement", {}, command_id);
1626 * Send click event to element
1628 * @param object aRequest
1629 * 'id' member holds the reference id to
1630 * the element that will be clicked
1632 clickElement: function MDA_clickElementent(aRequest) {
1633 let command_id = this.command_id = this.getCommandId();
1634 if (this.context == "chrome") {
1636 //NOTE: click atom fails, fall back to click() action
1637 let el = this.curBrowser.elementManager.getKnownElement(
1638 aRequest.parameters.id, this.getCurrentWindow());
1640 this.sendOk(command_id);
1643 this.sendError(e.message, e.code, e.stack, command_id);
1647 // We need to protect against the click causing an OOP frame to close.
1648 // This fires the mozbrowserclose event when it closes so we need to
1649 // listen for it and then just send an error back. The person making the
1650 // call should be aware something isnt right and handle accordingly
1651 let curWindow = this.getCurrentWindow();
1653 this.mozBrowserClose = function(e) {
1654 if (e.target.id == self.oopFrameId) {
1655 curWindow.removeEventListener('mozbrowserclose', self.mozBrowserClose, true);
1656 self.switchToGlobalMessageManager();
1657 self.sendError("The frame closed during the click, recovering to allow further communications", 55, null, command_id);
1660 curWindow.addEventListener('mozbrowserclose', this.mozBrowserClose, true);
1661 this.sendAsync("clickElement",
1662 { id: aRequest.parameters.id },
1668 * Get a given attribute of an element
1670 * @param object aRequest
1671 * 'id' member holds the reference id to
1672 * the element that will be inspected
1673 * 'name' member holds the name of the attribute to retrieve
1675 getElementAttribute: function MDA_getElementAttribute(aRequest) {
1676 let command_id = this.command_id = this.getCommandId();
1677 if (this.context == "chrome") {
1679 let el = this.curBrowser.elementManager.getKnownElement(
1680 aRequest.parameters.id, this.getCurrentWindow());
1681 this.sendResponse(utils.getElementAttribute(el, aRequest.parameters.name),
1685 this.sendError(e.message, e.code, e.stack, command_id);
1689 this.sendAsync("getElementAttribute",
1691 id: aRequest.parameters.id,
1692 name: aRequest.parameters.name
1699 * Get the text of an element, if any. Includes the text of all child elements.
1701 * @param object aRequest
1702 * 'id' member holds the reference id to
1703 * the element that will be inspected
1705 getElementText: function MDA_getElementText(aRequest) {
1706 let command_id = this.command_id = this.getCommandId();
1707 if (this.context == "chrome") {
1708 //Note: for chrome, we look at text nodes, and any node with a "label" field
1710 let el = this.curBrowser.elementManager.getKnownElement(
1711 aRequest.parameters.id, this.getCurrentWindow());
1713 this.getVisibleText(el, lines);
1714 lines = lines.join("\n");
1715 this.sendResponse(lines, command_id);
1718 this.sendError(e.message, e.code, e.stack, command_id);
1722 this.sendAsync("getElementText",
1723 { id: aRequest.parameters.id },
1729 * Get the tag name of the element.
1731 * @param object aRequest
1732 * 'id' member holds the reference id to
1733 * the element that will be inspected
1735 getElementTagName: function MDA_getElementTagName(aRequest) {
1736 let command_id = this.command_id = this.getCommandId();
1737 if (this.context == "chrome") {
1739 let el = this.curBrowser.elementManager.getKnownElement(
1740 aRequest.parameters.id, this.getCurrentWindow());
1741 this.sendResponse(el.tagName.toLowerCase(), command_id);
1744 this.sendError(e.message, e.code, e.stack, command_id);
1748 this.sendAsync("getElementTagName",
1749 { id: aRequest.parameters.id },
1755 * Check if element is displayed
1757 * @param object aRequest
1758 * 'id' member holds the reference id to
1759 * the element that will be checked
1761 isElementDisplayed: function MDA_isElementDisplayed(aRequest) {
1762 let command_id = this.command_id = this.getCommandId();
1763 if (this.context == "chrome") {
1765 let el = this.curBrowser.elementManager.getKnownElement(
1766 aRequest.parameters.id, this.getCurrentWindow());
1767 this.sendResponse(utils.isElementDisplayed(el), command_id);
1770 this.sendError(e.message, e.code, e.stack, command_id);
1774 this.sendAsync("isElementDisplayed",
1775 { id:aRequest.parameters.id },
1781 * Return the property of the computed style of an element
1783 * @param object aRequest
1784 * 'id' member holds the reference id to
1785 * the element that will be checked
1786 * 'propertyName' is the CSS rule that is being requested
1788 getElementValueOfCssProperty: function MDA_getElementValueOfCssProperty(aRequest){
1789 let command_id = this.command_id = this.getCommandId();
1790 this.sendAsync("getElementValueOfCssProperty",
1791 {id: aRequest.parameters.id, propertyName: aRequest.parameters.propertyName},
1796 * Submit a form on a content page by either using form or element in a form
1797 * @param object aRequest
1798 * 'id' member holds the reference id to
1799 * the element that will be checked
1801 submitElement: function MDA_submitElement(aRequest) {
1802 let command_id = this.command_id = this.getCommandId();
1803 if (this.context == "chrome") {
1804 this.sendError("Command 'submitElement' is not available in chrome context", 500, null, this.command_id);
1807 this.sendAsync("submitElement", {id: aRequest.parameters.id}, command_id);
1812 * Check if element is enabled
1814 * @param object aRequest
1815 * 'id' member holds the reference id to
1816 * the element that will be checked
1818 isElementEnabled: function MDA_isElementEnabled(aRequest) {
1819 let command_id = this.command_id = this.getCommandId();
1820 if (this.context == "chrome") {
1822 //Selenium atom doesn't quite work here
1823 let el = this.curBrowser.elementManager.getKnownElement(
1824 aRequest.parameters.id, this.getCurrentWindow());
1825 if (el.disabled != undefined) {
1826 this.sendResponse(!!!el.disabled, command_id);
1829 this.sendResponse(true, command_id);
1833 this.sendError(e.message, e.code, e.stack, command_id);
1837 this.sendAsync("isElementEnabled",
1838 { id:aRequest.parameters.id },
1844 * Check if element is selected
1846 * @param object aRequest
1847 * 'id' member holds the reference id to
1848 * the element that will be checked
1850 isElementSelected: function MDA_isElementSelected(aRequest) {
1851 let command_id = this.command_id = this.getCommandId();
1852 if (this.context == "chrome") {
1854 //Selenium atom doesn't quite work here
1855 let el = this.curBrowser.elementManager.getKnownElement(
1856 aRequest.parameters.id, this.getCurrentWindow());
1857 if (el.checked != undefined) {
1858 this.sendResponse(!!el.checked, command_id);
1860 else if (el.selected != undefined) {
1861 this.sendResponse(!!el.selected, command_id);
1864 this.sendResponse(true, command_id);
1868 this.sendError(e.message, e.code, e.stack, command_id);
1872 this.sendAsync("isElementSelected",
1873 { id:aRequest.parameters.id },
1878 getElementSize: function MDA_getElementSize(aRequest) {
1879 let command_id = this.command_id = this.getCommandId();
1880 if (this.context == "chrome") {
1882 let el = this.curBrowser.elementManager.getKnownElement(
1883 aRequest.parameters.id, this.getCurrentWindow());
1884 let clientRect = el.getBoundingClientRect();
1885 this.sendResponse({width: clientRect.width, height: clientRect.height},
1889 this.sendError(e.message, e.code, e.stack, command_id);
1893 this.sendAsync("getElementSize",
1894 { id:aRequest.parameters.id },
1899 getElementRect: function MDA_getElementRect(aRequest) {
1900 let command_id = this.command_id = this.getCommandId();
1901 if (this.context == "chrome") {
1903 let el = this.curBrowser.elementManager.getKnownElement(
1904 aRequest.parameters.id, this.getCurrentWindow());
1905 let clientRect = el.getBoundingClientRect();
1906 this.sendResponse({x: clientRect.x + this.getCurrentWindow().pageXOffset,
1907 y: clientRect.y + this.getCurrentWindow().pageYOffset,
1908 width: clientRect.width, height: clientRect.height},
1912 this.sendError(e.message, e.code, e.stack, command_id);
1916 this.sendAsync("getElementRect",
1917 { id:aRequest.parameters.id },
1923 * Send key presses to element after focusing on it
1925 * @param object aRequest
1926 * 'id' member holds the reference id to
1927 * the element that will be checked
1928 * 'value' member holds the value to send to the element
1930 sendKeysToElement: function MDA_sendKeysToElement(aRequest) {
1931 let command_id = this.command_id = this.getCommandId();
1932 if (this.context == "chrome") {
1934 let el = this.curBrowser.elementManager.getKnownElement(
1935 aRequest.parameters.id, this.getCurrentWindow());
1937 utils.sendString(aRequest.parameters.value.join(""), utils.window);
1938 this.sendOk(command_id);
1941 this.sendError(e.message, e.code, e.stack, command_id);
1945 this.sendAsync("sendKeysToElement",
1947 id:aRequest.parameters.id,
1948 value: aRequest.parameters.value
1955 * Sets the test name
1957 * The test name is used in logging messages.
1959 setTestName: function MDA_setTestName(aRequest) {
1960 this.command_id = this.getCommandId();
1961 this.logRequest("setTestName", aRequest);
1962 this.testName = aRequest.parameters.value;
1963 this.sendAsync("setTestName",
1964 { value: aRequest.parameters.value },
1969 * Clear the text of an element
1971 * @param object aRequest
1972 * 'id' member holds the reference id to
1973 * the element that will be cleared
1975 clearElement: function MDA_clearElement(aRequest) {
1976 let command_id = this.command_id = this.getCommandId();
1977 if (this.context == "chrome") {
1978 //the selenium atom doesn't work here
1980 let el = this.curBrowser.elementManager.getKnownElement(
1981 aRequest.parameters.id, this.getCurrentWindow());
1982 if (el.nodeName == "textbox") {
1985 else if (el.nodeName == "checkbox") {
1988 this.sendOk(command_id);
1991 this.sendError(e.message, e.code, e.stack, command_id);
1995 this.sendAsync("clearElement",
1996 { id:aRequest.parameters.id },
2002 * Get an element's location on the page.
2004 * The returned point will contain the x and y coordinates of the
2005 * top left-hand corner of the given element. The point (0,0)
2006 * refers to the upper-left corner of the document.
2008 * @return a point containing x and y coordinates as properties
2010 getElementLocation: function MDA_getElementLocation(aRequest) {
2011 this.command_id = this.getCommandId();
2012 this.sendAsync("getElementLocation", {id: aRequest.parameters.id},
2017 * Add a cookie to the document.
2019 addCookie: function MDA_addCookie(aRequest) {
2020 this.command_id = this.getCommandId();
2021 this.sendAsync("addCookie",
2022 { cookie:aRequest.parameters.cookie },
2027 * Get all the cookies for the current domain.
2029 * This is the equivalent of calling "document.cookie" and parsing
2032 getCookies: function MDA_getCookies() {
2033 this.command_id = this.getCommandId();
2034 this.sendAsync("getCookies", {}, this.command_id);
2038 * Delete all cookies that are visible to a document
2040 deleteAllCookies: function MDA_deleteAllCookies() {
2041 this.command_id = this.getCommandId();
2042 this.sendAsync("deleteAllCookies", {}, this.command_id);
2046 * Delete a cookie by name
2048 deleteCookie: function MDA_deleteCookie(aRequest) {
2049 this.command_id = this.getCommandId();
2050 this.sendAsync("deleteCookie",
2051 { name:aRequest.parameters.name },
2056 * Close the current window, ending the session if it's the last
2057 * window currently open.
2059 * On B2G this method is a noop and will return immediately.
2061 close: function MDA_close() {
2062 let command_id = this.command_id = this.getCommandId();
2063 if (appName == "B2G") {
2064 // We can't close windows so just return
2065 this.sendOk(command_id);
2068 // Get the total number of windows
2069 let numOpenWindows = 0;
2070 let winEnum = this.getWinEnumerator();
2071 while (winEnum.hasMoreElements()) {
2072 numOpenWindows += 1;
2076 // if there is only 1 window left, delete the session
2077 if (numOpenWindows === 1) {
2079 this.sessionTearDown();
2082 this.sendError("Could not clear session", 500,
2083 e.name + ": " + e.message, command_id);
2086 this.sendOk(command_id);
2091 this.messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
2092 this.getCurrentWindow().close();
2093 this.sendOk(command_id);
2096 this.sendError("Could not close window: " + e.message, 13, e.stack,
2103 * Deletes the session.
2105 * If it is a desktop environment, it will close the session's tab and close all listeners
2107 * If it is a B2G environment, it will make the main content listener sleep, and close
2108 * all other listeners. The main content listener persists after disconnect (it's the homescreen),
2109 * and can safely be reused.
2111 sessionTearDown: function MDA_sessionTearDown() {
2112 if (this.curBrowser != null) {
2113 if (appName == "B2G") {
2114 this.globalMessageManager.broadcastAsyncMessage(
2115 "Marionette:sleepSession" + this.curBrowser.mainContentId, {});
2116 this.curBrowser.knownFrames.splice(
2117 this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1);
2120 //don't set this pref for B2G since the framescript can be safely reused
2121 Services.prefs.setBoolPref("marionette.contentListener", false);
2123 this.curBrowser.closeTab();
2124 //delete session in each frame in each browser
2125 for (let win in this.browsers) {
2126 for (let i in this.browsers[win].knownFrames) {
2127 this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
2130 let winEnum = this.getWinEnumerator();
2131 while (winEnum.hasMoreElements()) {
2132 winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
2134 this.curBrowser.frameManager.removeMessageManagerListeners(this.globalMessageManager);
2136 this.switchToGlobalMessageManager();
2137 // reset frame to the top-most frame
2138 this.curFrame = null;
2139 if (this.mainFrame) {
2140 this.mainFrame.focus();
2142 this.deleteFile('marionetteChromeScripts');
2143 this.deleteFile('marionetteContentScripts');
2147 * Processes the 'deleteSession' request from the client by tearing down
2148 * the session and responding 'ok'.
2150 deleteSession: function MDA_deleteSession() {
2151 let command_id = this.command_id = this.getCommandId();
2153 this.sessionTearDown();
2156 this.sendError("Could not delete session", 500, e.name + ": " + e.message, command_id);
2159 this.sendOk(command_id);
2163 * Returns the current status of the Application Cache
2165 getAppCacheStatus: function MDA_getAppCacheStatus(aRequest) {
2166 this.command_id = this.getCommandId();
2167 this.sendAsync("getAppCacheStatus", {}, this.command_id);
2172 runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
2174 if (!this._emu_cbs) {
2177 this._emu_cbs[this._emu_cb_id] = callback;
2179 this.sendToClient({emulator_cmd: cmd, id: this._emu_cb_id}, -1);
2180 this._emu_cb_id += 1;
2183 runEmulatorShell: function runEmulatorShell(args, callback) {
2185 if (!this._emu_cbs) {
2188 this._emu_cbs[this._emu_cb_id] = callback;
2190 this.sendToClient({emulator_shell: args, id: this._emu_cb_id}, -1);
2191 this._emu_cb_id += 1;
2194 emulatorCmdResult: function emulatorCmdResult(message) {
2195 if (this.context != "chrome") {
2196 this.sendAsync("emulatorCmdResult", message, -1);
2200 if (!this._emu_cbs) {
2204 let cb = this._emu_cbs[message.id];
2205 delete this._emu_cbs[message.id];
2213 this.sendError(e.message, e.code, e.stack, -1);
2218 importScript: function MDA_importScript(aRequest) {
2219 let command_id = this.command_id = this.getCommandId();
2221 Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
2222 createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
2223 converter.charset = "UTF-8";
2225 let data = converter.convertToByteArray(aRequest.parameters.script, result);
2226 let ch = Components.classes["@mozilla.org/security/hash;1"]
2227 .createInstance(Components.interfaces.nsICryptoHash);
2229 ch.update(data, data.length);
2230 let hash = ch.finish(true);
2231 if (this.importedScriptHashes[this.context].indexOf(hash) > -1) {
2232 //we have already imported this script
2233 this.sendOk(command_id);
2236 this.importedScriptHashes[this.context].push(hash);
2237 if (this.context == "chrome") {
2239 if (this.importedScripts.exists()) {
2240 file = FileUtils.openFileOutputStream(this.importedScripts,
2241 FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY);
2244 //Note: The permission bits here don't actually get set (bug 804563)
2245 this.importedScripts.createUnique(
2246 Components.interfaces.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
2247 file = FileUtils.openFileOutputStream(this.importedScripts,
2248 FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
2249 this.importedScripts.permissions = parseInt("0666", 8); //actually set permissions
2251 file.write(aRequest.parameters.script, aRequest.parameters.script.length);
2253 this.sendOk(command_id);
2256 this.sendAsync("importScript",
2257 { script: aRequest.parameters.script },
2262 clearImportedScripts: function MDA_clearImportedScripts(aRequest) {
2263 let command_id = this.command_id = this.getCommandId();
2265 if (this.context == "chrome") {
2266 this.deleteFile('marionetteChromeScripts');
2269 this.deleteFile('marionetteContentScripts');
2273 this.sendError("Could not clear imported scripts", 500, e.name + ": " + e.message, command_id);
2276 this.sendOk(command_id);
2280 * Takes a screenshot of a web element or the current frame.
2282 * The screen capture is returned as a lossless PNG image encoded as
2283 * a base 64 string. If the <code>id</code> argument is not null
2284 * and refers to a present and visible web element's ID, the capture
2285 * area will be limited to the bounding box of that element.
2286 * Otherwise, the capture area will be the bounding box of the
2289 * @param id an optional reference to a web element
2290 * @param highlights an optional list of web elements to draw a red
2291 * box around in the returned capture
2292 * @return PNG image encoded as base 64 string
2294 takeScreenshot: function MDA_takeScreenshot(aRequest) {
2295 this.command_id = this.getCommandId();
2296 this.sendAsync("takeScreenshot",
2297 {id: aRequest.parameters.id,
2298 highlights: aRequest.parameters.highlights},
2303 * Get the current browser orientation.
2305 * Will return one of the valid primary orientation values
2306 * portrait-primary, landscape-primary, portrait-secondary, or
2307 * landscape-secondary.
2309 getScreenOrientation: function MDA_getScreenOrientation(aRequest) {
2310 this.command_id = this.getCommandId();
2311 let curWindow = this.getCurrentWindow();
2312 let or = curWindow.screen.mozOrientation;
2313 this.sendResponse(or, this.command_id);
2317 * Set the current browser orientation.
2319 * The supplied orientation should be given as one of the valid
2320 * orientation values. If the orientation is unknown, an error will
2323 * Valid orientations are "portrait" and "landscape", which fall
2324 * back to "portrait-primary" and "landscape-primary" respectively,
2325 * and "portrait-secondary" as well as "landscape-secondary".
2327 setScreenOrientation: function MDA_setScreenOrientation(aRequest) {
2328 const ors = ["portrait", "landscape",
2329 "portrait-primary", "landscape-primary",
2330 "portrait-secondary", "landscape-secondary"];
2332 this.command_id = this.getCommandId();
2333 let or = String(aRequest.parameters.orientation);
2335 let mozOr = or.toLowerCase();
2336 if (ors.indexOf(mozOr) < 0) {
2337 this.sendError("Unknown screen orientation: " + or, 500, null,
2342 let curWindow = this.getCurrentWindow();
2343 if (!curWindow.screen.mozLockOrientation(mozOr)) {
2344 this.sendError("Unable to set screen orientation: " + or, 500,
2345 null, this.command_id);
2347 this.sendOk(this.command_id);
2351 * Helper function to convert an outerWindowID into a UID that Marionette
2354 generateFrameId: function MDA_generateFrameId(id) {
2355 let uid = id + (appName == "B2G" ? "-b2g" : "");
2360 * Receives all messages from content messageManager
2362 receiveMessage: function MDA_receiveMessage(message) {
2363 // We need to just check if we need to remove the mozbrowserclose listener
2364 if (this.mozBrowserClose !== null){
2365 let curWindow = this.getCurrentWindow();
2366 curWindow.removeEventListener('mozbrowserclose', this.mozBrowserClose, true);
2367 this.mozBrowserClose = null;
2370 switch (message.name) {
2371 case "Marionette:done":
2372 this.sendResponse(message.json.value, message.json.command_id);
2374 case "Marionette:ok":
2375 this.sendOk(message.json.command_id);
2377 case "Marionette:error":
2378 this.sendError(message.json.message, message.json.status, message.json.stacktrace, message.json.command_id);
2380 case "Marionette:log":
2381 //log server-side messages
2382 logger.info(message.json.message);
2384 case "Marionette:shareData":
2385 //log messages from tests
2386 if (message.json.log) {
2387 this.marionetteLog.addLogs(message.json.log);
2390 case "Marionette:runEmulatorCmd":
2391 case "Marionette:runEmulatorShell":
2392 this.sendToClient(message.json, -1);
2394 case "Marionette:switchToFrame":
2395 this.oopFrameId = this.curBrowser.frameManager.switchToFrame(message);
2396 this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get();
2398 case "Marionette:switchToModalOrigin":
2399 this.curBrowser.frameManager.switchToModalOrigin(message);
2400 this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get();
2402 case "Marionette:switchedToFrame":
2403 logger.info("Switched to frame: " + JSON.stringify(message.json));
2404 if (message.json.restorePrevious) {
2405 this.currentFrameElement = this.previousFrameElement;
2408 if (message.json.storePrevious) {
2409 // we don't arbitrarily save previousFrameElement, since
2410 // we allow frame switching after modals appear, which would
2411 // override this value and we'd lose our reference
2412 this.previousFrameElement = this.currentFrameElement;
2414 this.currentFrameElement = message.json.frameValue;
2417 case "Marionette:register":
2418 // This code processes the content listener's registration information
2419 // and either accepts the listener, or ignores it
2420 let nullPrevious = (this.curBrowser.curFrameId == null);
2421 let listenerWindow =
2422 Services.wm.getOuterWindowWithId(message.json.value);
2424 //go in here if we're already in a remote frame.
2425 if ((!listenerWindow || (listenerWindow.location &&
2426 listenerWindow.location.href != message.json.href)) &&
2427 (this.curBrowser.frameManager.currentRemoteFrame !== null)) {
2428 // The outerWindowID from an OOP frame will not be meaningful to
2429 // the parent process here, since each process maintains its own
2430 // independent window list. So, it will either be null (!listenerWindow)
2431 // if we're already in a remote frame,
2432 // or it will point to some random window, which will hopefully
2433 // cause an href mismatch. Currently this only happens
2434 // in B2G for OOP frames registered in Marionette:switchToFrame, so
2435 // we'll acknowledge the switchToFrame message here.
2436 // XXX: Should have a better way of determining that this message
2437 // is from a remote frame.
2438 this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value);
2439 this.sendOk(this.command_id);
2444 browserType = message.target.getAttribute("type");
2446 // browserType remains undefined.
2449 // this will be sent to tell the content process if it is the main content
2450 let mainContent = (this.curBrowser.mainContentId == null);
2451 if (!browserType || browserType != "content") {
2452 //curBrowser holds all the registered frames in knownFrames
2453 reg.id = this.curBrowser.register(this.generateFrameId(message.json.value),
2456 // set to true if we updated mainContentId
2457 mainContent = ((mainContent == true) && (this.curBrowser.mainContentId != null));
2459 this.mainContentFrameId = this.curBrowser.curFrameId;
2461 this.curBrowser.elementManager.seenItems[reg.id] = Cu.getWeakReference(listenerWindow);
2462 if (nullPrevious && (this.curBrowser.curFrameId != null)) {
2463 if (!this.sendAsync("newSession",
2464 { B2G: (appName == "B2G") },
2465 this.newSessionCommandId)) {
2468 if (this.curBrowser.newSession) {
2469 this.getSessionCapabilities();
2470 this.newSessionCommandId = null;
2473 return [reg, mainContent];
2474 case "Marionette:emitTouchEvent":
2475 let globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
2476 .getService(Ci.nsIMessageBroadcaster);
2477 globalMessageManager.broadcastAsyncMessage(
2478 "MarionetteMainListener:emitTouchEvent", message.json);
2484 MarionetteServerConnection.prototype.requestTypes = {
2485 "getMarionetteID": MarionetteServerConnection.prototype.getMarionetteID,
2486 "sayHello": MarionetteServerConnection.prototype.sayHello,
2487 "newSession": MarionetteServerConnection.prototype.newSession,
2488 "getSessionCapabilities": MarionetteServerConnection.prototype.getSessionCapabilities,
2489 "log": MarionetteServerConnection.prototype.log,
2490 "getLogs": MarionetteServerConnection.prototype.getLogs,
2491 "setContext": MarionetteServerConnection.prototype.setContext,
2492 "executeScript": MarionetteServerConnection.prototype.execute,
2493 "setScriptTimeout": MarionetteServerConnection.prototype.setScriptTimeout,
2494 "timeouts": MarionetteServerConnection.prototype.timeouts,
2495 "singleTap": MarionetteServerConnection.prototype.singleTap,
2496 "actionChain": MarionetteServerConnection.prototype.actionChain,
2497 "multiAction": MarionetteServerConnection.prototype.multiAction,
2498 "executeAsyncScript": MarionetteServerConnection.prototype.executeWithCallback,
2499 "executeJSScript": MarionetteServerConnection.prototype.executeJSScript,
2500 "setSearchTimeout": MarionetteServerConnection.prototype.setSearchTimeout,
2501 "findElement": MarionetteServerConnection.prototype.findElement,
2502 "findElements": MarionetteServerConnection.prototype.findElements,
2503 "clickElement": MarionetteServerConnection.prototype.clickElement,
2504 "getElementAttribute": MarionetteServerConnection.prototype.getElementAttribute,
2505 "getElementText": MarionetteServerConnection.prototype.getElementText,
2506 "getElementTagName": MarionetteServerConnection.prototype.getElementTagName,
2507 "isElementDisplayed": MarionetteServerConnection.prototype.isElementDisplayed,
2508 "getElementValueOfCssProperty": MarionetteServerConnection.prototype.getElementValueOfCssProperty,
2509 "submitElement": MarionetteServerConnection.prototype.submitElement,
2510 "getElementSize": MarionetteServerConnection.prototype.getElementSize, //deprecated
2511 "getElementRect": MarionetteServerConnection.prototype.getElementRect,
2512 "isElementEnabled": MarionetteServerConnection.prototype.isElementEnabled,
2513 "isElementSelected": MarionetteServerConnection.prototype.isElementSelected,
2514 "sendKeysToElement": MarionetteServerConnection.prototype.sendKeysToElement,
2515 "getElementLocation": MarionetteServerConnection.prototype.getElementLocation, // deprecated
2516 "getElementPosition": MarionetteServerConnection.prototype.getElementLocation, // deprecated
2517 "clearElement": MarionetteServerConnection.prototype.clearElement,
2518 "getTitle": MarionetteServerConnection.prototype.getTitle,
2519 "getWindowType": MarionetteServerConnection.prototype.getWindowType,
2520 "getPageSource": MarionetteServerConnection.prototype.getPageSource,
2521 "get": MarionetteServerConnection.prototype.get,
2522 "goUrl": MarionetteServerConnection.prototype.get, // deprecated
2523 "getCurrentUrl": MarionetteServerConnection.prototype.getCurrentUrl,
2524 "getUrl": MarionetteServerConnection.prototype.getCurrentUrl, // deprecated
2525 "goBack": MarionetteServerConnection.prototype.goBack,
2526 "goForward": MarionetteServerConnection.prototype.goForward,
2527 "refresh": MarionetteServerConnection.prototype.refresh,
2528 "getWindowHandle": MarionetteServerConnection.prototype.getWindowHandle,
2529 "getCurrentWindowHandle": MarionetteServerConnection.prototype.getWindowHandle, // Selenium 2 compat
2530 "getWindow": MarionetteServerConnection.prototype.getWindowHandle, // deprecated
2531 "getWindowHandles": MarionetteServerConnection.prototype.getWindowHandles,
2532 "getCurrentWindowHandles": MarionetteServerConnection.prototype.getWindowHandles, // Selenium 2 compat
2533 "getWindows": MarionetteServerConnection.prototype.getWindowHandles, // deprecated
2534 "getActiveFrame": MarionetteServerConnection.prototype.getActiveFrame,
2535 "switchToFrame": MarionetteServerConnection.prototype.switchToFrame,
2536 "switchToWindow": MarionetteServerConnection.prototype.switchToWindow,
2537 "deleteSession": MarionetteServerConnection.prototype.deleteSession,
2538 "emulatorCmdResult": MarionetteServerConnection.prototype.emulatorCmdResult,
2539 "importScript": MarionetteServerConnection.prototype.importScript,
2540 "clearImportedScripts": MarionetteServerConnection.prototype.clearImportedScripts,
2541 "getAppCacheStatus": MarionetteServerConnection.prototype.getAppCacheStatus,
2542 "close": MarionetteServerConnection.prototype.close,
2543 "closeWindow": MarionetteServerConnection.prototype.close, // deprecated
2544 "setTestName": MarionetteServerConnection.prototype.setTestName,
2545 "takeScreenshot": MarionetteServerConnection.prototype.takeScreenshot,
2546 "screenShot": MarionetteServerConnection.prototype.takeScreenshot, // deprecated
2547 "screenshot": MarionetteServerConnection.prototype.takeScreenshot, // Selenium 2 compat
2548 "addCookie": MarionetteServerConnection.prototype.addCookie,
2549 "getCookies": MarionetteServerConnection.prototype.getCookies,
2550 "getAllCookies": MarionetteServerConnection.prototype.getCookies, // deprecated
2551 "deleteAllCookies": MarionetteServerConnection.prototype.deleteAllCookies,
2552 "deleteCookie": MarionetteServerConnection.prototype.deleteCookie,
2553 "getActiveElement": MarionetteServerConnection.prototype.getActiveElement,
2554 "getScreenOrientation": MarionetteServerConnection.prototype.getScreenOrientation,
2555 "setScreenOrientation": MarionetteServerConnection.prototype.setScreenOrientation
2559 * Creates a BrowserObj. BrowserObjs handle interactions with the
2560 * browser, according to the current environment (desktop, b2g, etc.)
2562 * @param nsIDOMWindow win
2563 * The window whose browser needs to be accessed
2566 function BrowserObj(win, server) {
2567 this.DESKTOP = "desktop";
2570 this.tab = null; //Holds a reference to the created tab, if any
2572 this.knownFrames = [];
2573 this.curFrameId = null;
2574 this.startPage = "about:blank";
2575 this.mainContentId = null; // used in B2G to identify the homescreen content page
2576 this.newSession = true; //used to set curFrameId upon new session
2577 this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
2578 this.setBrowser(win);
2579 this.frameManager = new FrameManager(server); //We should have one FM per BO so that we can handle modals in each Browser
2581 //register all message listeners
2582 this.frameManager.addMessageManagerListeners(server.messageManager);
2585 BrowserObj.prototype = {
2587 * Set the browser if the application is not B2G
2589 * @param nsIDOMWindow win
2590 * current window reference
2592 setBrowser: function BO_setBrowser(win) {
2595 if (this.window.location.href.indexOf("chrome://b2g") == -1) {
2596 this.browser = win.gBrowser;
2604 this.browser = win.BrowserApp;
2609 * Called when we start a session with this browser.
2611 * In a desktop environment, if newTab is true, it will start
2612 * a new 'about:blank' tab and change focus to this tab.
2614 * This will also set the active messagemanager for this object
2616 * @param boolean newTab
2617 * If true, create new tab
2619 startSession: function BO_startSession(newTab, win, callback) {
2620 if (appName != "Firefox") {
2621 callback(win, newTab);
2624 this.tab = this.addTab(this.startPage);
2625 //if we have a new tab, make it the selected tab
2626 this.browser.selectedTab = this.tab;
2627 let newTabBrowser = this.browser.getBrowserForTab(this.tab);
2628 // wait for tab to be loaded
2629 newTabBrowser.addEventListener("load", function onLoad() {
2630 newTabBrowser.removeEventListener("load", onLoad, true);
2631 callback(win, newTab);
2635 //set this.tab to the currently focused tab
2636 if (this.browser != undefined && this.browser.selectedTab != undefined) {
2637 this.tab = this.browser.selectedTab;
2639 callback(win, newTab);
2644 * Closes current tab
2646 closeTab: function BO_closeTab() {
2648 this.browser.removeTab &&
2649 this.tab != null && (appName != "B2G")) {
2650 this.browser.removeTab(this.tab);
2656 * Opens a tab with given uri
2661 addTab: function BO_addTab(uri) {
2662 return this.browser.addTab(uri, true);
2666 * Loads content listeners if we don't already have them
2668 * @param string script
2669 * path of script to load
2670 * @param nsIDOMWindow frame
2671 * frame to load the script in
2673 loadFrameScript: function BO_loadFrameScript(script, frame) {
2674 frame.window.messageManager.loadFrameScript(script, true, true);
2675 Services.prefs.setBoolPref("marionette.contentListener", true);
2679 * Registers a new frame, and sets its current frame id to this frame
2680 * if it is not already assigned, and if a) we already have a session
2681 * or b) we're starting a new session and it is the right start frame.
2685 * @param object frameWindow
2686 * the DOMWindow object of the frame that's being registered
2688 register: function BO_register(uid, frameWindow) {
2689 if (this.curFrameId == null) {
2690 // If we're setting up a new session on Firefox, we only process the
2691 // registration for this frame if it belongs to the tab we've just
2693 if ((!this.newSession) ||
2695 ((appName != "Firefox") ||
2696 frameWindow == this.browser.getBrowserForTab(this.tab).contentWindow))) {
2697 this.curFrameId = uid;
2698 this.mainContentId = uid;
2701 this.knownFrames.push(uid); //used to delete sessions
2707 * Marionette server -- this class holds a reference to a socket and creates
2708 * MarionetteServerConnection objects as needed.
2710 this.MarionetteServer = function MarionetteServer(port, forceLocal) {
2711 let flags = Ci.nsIServerSocket.KeepWhenOffline;
2713 flags |= Ci.nsIServerSocket.LoopbackOnly;
2715 let socket = new ServerSocket(port, flags, 0);
2716 logger.info("Listening on port " + socket.port + "\n");
2717 socket.asyncListen(this);
2718 this.listener = socket;
2719 this.nextConnId = 0;
2720 this.connections = {};
2723 MarionetteServer.prototype = {
2724 onSocketAccepted: function(serverSocket, clientSocket)
2726 logger.debug("accepted connection on " + clientSocket.host + ":" + clientSocket.port);
2728 let input = clientSocket.openInputStream(0, 0, 0);
2729 let output = clientSocket.openOutputStream(0, 0, 0);
2730 let aTransport = new DebuggerTransport(input, output);
2731 let connID = "conn" + this.nextConnID++ + '.';
2732 let conn = new MarionetteServerConnection(connID, aTransport, this);
2733 this.connections[connID] = conn;
2735 // Create a root actor for the connection and send the hello packet.
2740 closeListener: function() {
2741 this.listener.close();
2742 this.listener = null;
2745 _connectionClosed: function DS_connectionClosed(aConnection) {
2746 delete this.connections[aConnection.prefix];