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/. */
8 * Gecko-specific actors.
11 const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
13 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
15 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
16 .getService(Ci.mozIJSSubScriptLoader);
17 loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
18 loader.loadSubScript("chrome://marionette/content/marionette-log-obj.js");
19 loader.loadSubScript("chrome://marionette/content/marionette-perf.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 let specialpowers = {};
27 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
29 specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver();
30 specialpowers.specialPowersObserver.init();
32 Cu.import("resource://gre/modules/Services.jsm");
33 Cu.import("resource://gre/modules/FileUtils.jsm");
34 Cu.import("resource://gre/modules/NetUtil.jsm");
36 Services.prefs.setBoolPref("marionette.contentListener", false);
37 let appName = Services.appinfo.name;
40 Cu.import("resource://gre/modules/services-common/log4moz.js");
41 let logger = Log4Moz.repository.getLogger("Marionette");
42 logger.info('marionette-actors.js loaded');
44 Services.prefs.setBoolPref("network.gonk.manage-offline-status", false);
45 Services.io.manageOfflineStatus = false;
46 Services.io.offline = false;
48 // This is used to prevent newSession from returning before the telephony
49 // API's are ready; see bug 792647. This assumes that marionette-actors.js
50 // will be loaded before the 'system-message-listener-ready' message
51 // is fired. If this stops being true, this approach will have to change.
52 let systemMessageListenerReady = false;
53 Services.obs.addObserver(function() {
54 systemMessageListenerReady = true;
55 }, "system-message-listener-ready", false);
58 * Creates the root actor once a connection is established
61 function createRootActor(aConnection)
63 return new MarionetteRootActor(aConnection);
67 * Root actor for Marionette. Add any future actors to its actor pool.
68 * Implements methods needed by resource:///modules/devtools/dbg-server.jsm
71 function MarionetteRootActor(aConnection)
73 this.conn = aConnection;
74 this._marionetteActor = new MarionetteDriverActor(this.conn);
75 this._marionetteActorPool = null; //hold future actors
77 this._marionetteActorPool = new ActorPool(this.conn);
78 this._marionetteActorPool.addActor(this._marionetteActor);
79 this.conn.addActorPool(this._marionetteActorPool);
82 MarionetteRootActor.prototype = {
84 * Called when a client first makes a connection
87 * returns the name of the actor, the application type, and any traits
89 sayHello: function MRA_sayHello() {
90 return { from: "root",
91 applicationType: "gecko",
96 * Called when client disconnects. Cleans up marionette driver actions.
98 disconnect: function MRA_disconnect() {
99 this._marionetteActor.deleteSession();
103 * Used to get the running marionette actor, so we can issue commands
106 * Returns the ID the client can use to communicate with the
107 * MarionetteDriverActor
109 getMarionetteID: function MRA_getMarionette() {
110 return { "from": "root",
111 "id": this._marionetteActor.actorID } ;
115 // register the calls
116 MarionetteRootActor.prototype.requestTypes = {
117 "getMarionetteID": MarionetteRootActor.prototype.getMarionetteID,
118 "sayHello": MarionetteRootActor.prototype.sayHello
122 * An object representing a frame that Marionette has loaded a
125 function MarionetteRemoteFrame(windowId, frameId) {
126 this.windowId = windowId;
127 this.frameId = frameId;
128 this.targetFrameId = null;
129 this.messageManager = null;
130 this.command_id = null;
132 // persistent list of remote frames that Marionette has loaded a frame script in
133 let remoteFrames = [];
136 * This actor is responsible for all marionette API calls. It gets created
137 * for each connection and manages all chrome and browser based calls. It
138 * mediates content calls by issuing appropriate messages to the content process.
140 function MarionetteDriverActor(aConnection)
142 this.uuidGen = Cc["@mozilla.org/uuid-generator;1"]
143 .getService(Ci.nsIUUIDGenerator);
145 this.conn = aConnection;
146 this.globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
147 .getService(Ci.nsIMessageBroadcaster);
148 this.messageManager = this.globalMessageManager;
149 this.browsers = {}; //holds list of BrowserObjs
150 this.curBrowser = null; // points to current browser
151 this.context = "content";
152 this.scriptTimeout = null;
153 this.pageTimeout = null;
155 this.marionetteLog = new MarionetteLogObj();
156 this.marionettePerf = new MarionettePerfData();
157 this.command_id = null;
158 this.mainFrame = null; //topmost chrome frame
159 this.curFrame = null; //subframe that currently has focus
160 this.importedScripts = FileUtils.getFile('TmpD', ['marionettescriptchrome']);
161 this.currentRemoteFrame = null; // a member of remoteFrames
162 this.testName = null;
163 this.mozBrowserClose = null;
165 //register all message listeners
166 this.addMessageManagerListeners(this.messageManager);
169 MarionetteDriverActor.prototype = {
172 actorPrefix: "marionette",
179 * Switches to the global ChromeMessageBroadcaster, potentially replacing a frame-specific
180 * ChromeMessageSender. Has no effect if the global ChromeMessageBroadcaster is already
181 * in use. If this replaces a frame-specific ChromeMessageSender, it removes the message
182 * listeners from that sender, and then puts the corresponding frame script "to sleep",
183 * which removes most of the message listeners from it as well.
185 switchToGlobalMessageManager: function MDA_switchToGlobalMM() {
186 if (this.currentRemoteFrame !== null) {
187 this.removeMessageManagerListeners(this.messageManager);
189 // this can fail if the frame is already gone
190 this.sendAsync("sleepSession");
194 this.messageManager = this.globalMessageManager;
195 this.currentRemoteFrame = null;
199 * Helper method to send async messages to the content listener
202 * Suffix of the targetted message listener (Marionette:<suffix>)
203 * @param object values
204 * Object to send to the listener
206 sendAsync: function MDA_sendAsync(name, values) {
207 if (this.currentRemoteFrame !== null) {
208 this.messageManager.sendAsyncMessage(
209 "Marionette:" + name + this.currentRemoteFrame.targetFrameId, values);
212 this.messageManager.broadcastAsyncMessage(
213 "Marionette:" + name + this.curBrowser.curFrameId, values);
218 * Adds listeners for messages from content frame scripts.
220 * @param object messageManager
221 * The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
222 * to which the listeners should be added.
224 addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) {
225 messageManager.addMessageListener("Marionette:ok", this);
226 messageManager.addMessageListener("Marionette:done", this);
227 messageManager.addMessageListener("Marionette:error", this);
228 messageManager.addMessageListener("Marionette:log", this);
229 messageManager.addMessageListener("Marionette:shareData", this);
230 messageManager.addMessageListener("Marionette:register", this);
231 messageManager.addMessageListener("Marionette:runEmulatorCmd", this);
232 messageManager.addMessageListener("Marionette:switchToFrame", this);
236 * Removes listeners for messages from content frame scripts.
238 * @param object messageManager
239 * The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
240 * from which the listeners should be removed.
242 removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) {
243 messageManager.removeMessageListener("Marionette:ok", this);
244 messageManager.removeMessageListener("Marionette:done", this);
245 messageManager.removeMessageListener("Marionette:error", this);
246 messageManager.removeMessageListener("Marionette:log", this);
247 messageManager.removeMessageListener("Marionette:shareData", this);
248 messageManager.removeMessageListener("Marionette:register", this);
249 messageManager.removeMessageListener("Marionette:runEmulatorCmd", this);
250 messageManager.removeMessageListener("Marionette:switchToFrame", this);
253 logRequest: function MDA_logRequest(type, data) {
254 logger.debug("Got request: " + type + ", data: " + JSON.stringify(data) + ", id: " + this.command_id);
258 * Generic method to pass a response to the client
261 * Response to send back to client
262 * @param string command_id
263 * Unique identifier assigned to the client's request.
264 * Used to distinguish the asynchronous responses.
266 sendToClient: function MDA_sendToClient(msg, command_id) {
267 logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id +
268 ", " + this.command_id);
270 logger.warn("got a response with no command_id");
273 else if (command_id != -1) {
274 // A command_id of -1 is used for emulator callbacks, and those
275 // don't use this.command_id.
276 if (!this.command_id) {
277 // A null value for this.command_id means we've already processed
278 // a message for the previous value, and so the current message is a
280 logger.warn("ignoring duplicate response for command_id " + command_id);
283 else if (this.command_id != command_id) {
284 logger.warn("ignoring out-of-sync response");
289 if (command_id != -1) {
290 // Don't unset this.command_id if this message is to process an
291 // emulator callback, since another response for this command_id is
292 // expected, after the containing call to execute_async_script finishes.
293 this.command_id = null;
298 * Send a value to client
300 * @param object value
301 * Value to send back to client
302 * @param string command_id
303 * Unique identifier assigned to the client's request.
304 * Used to distinguish the asynchronous responses.
306 sendResponse: function MDA_sendResponse(value, command_id) {
307 if (typeof(value) == 'undefined')
309 this.sendToClient({from:this.actorID, value: value}, command_id);
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);
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] = 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();
475 * All methods implementing a command from the client should create a
476 * command_id, and then use this command_id in all messages exchanged with
477 * the frame scripts and with responses sent to the client. This prevents
478 * commands and responses from getting out-of-sync, which can happen in
479 * the case of execute_async calls that timeout and then later send a
480 * response, and other situations. See bug 779011. See setScriptTimeout()
481 * for a basic example.
485 * Create a new session. This creates a BrowserObj.
487 * In a desktop environment, this opens a new 'about:blank' tab for
488 * the client to test in.
491 newSession: function MDA_newSession() {
492 this.command_id = this.getCommandId();
493 this.newSessionCommandId = this.command_id;
495 this.scriptTimeout = 10000;
497 function waitForWindow() {
498 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
499 let win = this.getCurrentWindow();
501 (appName == "Firefox" && !win.gBrowser) ||
502 (appName == "Fennec" && !win.BrowserApp)) {
503 checkTimer.initWithCallback(waitForWindow.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
506 this.startBrowser(win, true);
510 this.switchToGlobalMessageManager();
512 if (!Services.prefs.getBoolPref("marionette.contentListener")) {
513 waitForWindow.call(this);
515 else if ((appName != "Firefox") && (this.curBrowser == null)) {
516 //if there is a content listener, then we just wake it up
517 this.addBrowser(this.getCurrentWindow());
518 this.curBrowser.startSession(false, this.getCurrentWindow(), this.whenBrowserStarted);
519 this.messageManager.broadcastAsyncMessage("Marionette:restart", {});
522 this.sendError("Session already running", 500, null, this.command_id);
526 getSessionCapabilities: function MDA_getSessionCapabilities(){
527 this.command_id = this.getCommandId();
529 let rotatable = appName == "B2G" ? true : false;
532 'appBuildId' : Services.appinfo.appBuildID,
533 'XULappId' : Services.appinfo.ID,
534 'cssSelectorsEnabled': true,
535 'browserName': appName,
536 'handlesAlerts': false,
537 'javascriptEnabled': true,
538 'nativeEvents': false,
539 'platform': Services.appinfo.OS,
540 'rotatable': rotatable,
541 'takesScreenshot': false,
542 'version': Services.appinfo.version
545 this.sendResponse(value, this.command_id);
548 getStatus: function MDA_getStatus(){
549 this.command_id = this.getCommandId();
553 arch = (Services.appinfo.XPCOMABI || 'unknown').split('-')[0]
562 'name': Services.appinfo.OS,
566 'revision': 'unknown',
567 'time': Services.appinfo.platformBuildID,
568 'version': Services.appinfo.version
572 this.sendResponse(value, this.command_id);
576 * Log message. Accepts user defined log-level.
578 * @param object aRequest
579 * 'value' member holds log message
580 * 'level' member hold log level
582 log: function MDA_log(aRequest) {
583 this.command_id = this.getCommandId();
584 this.marionetteLog.log(aRequest.value, aRequest.level);
585 this.sendOk(this.command_id);
589 * Return all logged messages.
591 getLogs: function MDA_getLogs() {
592 this.command_id = this.getCommandId();
593 this.sendResponse(this.marionetteLog.getLogs(), this.command_id);
597 * Log some performance data
599 addPerfData: function MDA_addPerfData(aRequest) {
600 this.command_id = this.getCommandId();
601 this.marionettePerf.addPerfData(aRequest.suite, aRequest.name, aRequest.value);
602 this.sendOk(this.command_id);
606 * Retrieve the performance data
608 getPerfData: function MDA_getPerfData() {
609 this.command_id = this.getCommandId();
610 this.sendResponse(this.marionettePerf.getPerfData(), this.command_id);
614 * Sets the context of the subsequent commands to be either 'chrome' or 'content'
616 * @param object aRequest
617 * 'value' member holds the name of the context to be switched to
619 setContext: function MDA_setContext(aRequest) {
620 this.command_id = this.getCommandId();
621 this.logRequest("setContext", aRequest);
622 let context = aRequest.value;
623 if (context != "content" && context != "chrome") {
624 this.sendError("invalid context", 500, null, this.command_id);
627 this.context = context;
628 this.sendOk(this.command_id);
633 * Returns a chrome sandbox that can be used by the execute_foo functions.
635 * @param nsIDOMWindow aWindow
636 * Window in which we will execute code
637 * @param Marionette marionette
638 * Marionette test instance
642 * Returns the sandbox
644 createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args, specialPowers, command_id) {
646 args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow);
649 this.sendError(e.message, e.code, e.stack, command_id);
653 let _chromeSandbox = new Cu.Sandbox(aWindow,
654 { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''});
655 _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
656 _chromeSandbox.__marionetteParams = args;
657 _chromeSandbox.testUtils = utils;
659 marionette.exports.forEach(function(fn) {
661 _chromeSandbox[fn] = marionette[fn].bind(marionette);
664 _chromeSandbox[fn] = marionette[fn];
668 _chromeSandbox.isSystemMessageListenerReady =
669 function() { return systemMessageListenerReady; }
671 if (specialPowers == true) {
672 loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js",
674 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js",
676 loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js",
680 return _chromeSandbox;
684 * Executes a script in the given sandbox.
686 * @param Sandbox sandbox
687 * Sandbox in which the script will run
688 * @param string script
690 * @param boolean directInject
691 * If true, then the script will be run as is,
692 * and not as a function body (as you would
693 * do using the WebDriver spec)
694 * @param boolean async
695 * True if the script is asynchronous
697 executeScriptInSandbox: function MDA_executeScriptInSandbox(sandbox, script,
698 directInject, async, command_id, timeout) {
700 if (directInject && async &&
701 (timeout == null || timeout == 0)) {
702 this.sendError("Please set a timeout", 21, null, command_id);
706 if (this.importedScripts.exists()) {
707 let stream = Cc["@mozilla.org/network/file-input-stream;1"].
708 createInstance(Ci.nsIFileInputStream);
709 stream.init(this.importedScripts, -1, 0, 0);
710 let data = NetUtil.readInputStreamToString(stream, stream.available());
711 script = data + script;
714 let res = Cu.evalInSandbox(script, sandbox, "1.8");
716 if (directInject && !async &&
717 (res == undefined || res.passed == undefined)) {
718 this.sendError("finish() not called", 500, null, command_id);
723 this.sendResponse(this.curBrowser.elementManager.wrapValue(res),
729 * Execute the given script either as a function body (executeScript)
730 * or directly (for 'mochitest' like JS Marionette tests)
732 * @param object aRequest
733 * 'value' member is the script to run
734 * 'args' member holds the arguments to the script
735 * @param boolean directInject
736 * if true, it will be run directly and not as a
739 execute: function MDA_execute(aRequest, directInject) {
740 let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
741 let command_id = this.command_id = this.getCommandId();
742 this.logRequest("execute", aRequest);
743 if (aRequest.newSandbox == undefined) {
744 //if client does not send a value in newSandbox,
745 //then they expect the same behaviour as webdriver
746 aRequest.newSandbox = true;
748 if (this.context == "content") {
749 this.sendAsync("executeScript", {value: aRequest.value,
751 newSandbox: aRequest.newSandbox,
753 command_id: command_id,
754 specialPowers: aRequest.specialPowers});
758 let curWindow = this.getCurrentWindow();
759 let marionette = new Marionette(this, curWindow, "chrome",
760 this.marionetteLog, this.marionettePerf,
761 timeout, this.testName);
762 let _chromeSandbox = this.createExecuteSandbox(curWindow,
765 aRequest.specialPowers,
771 _chromeSandbox.finish = function chromeSandbox_finish() {
772 return marionette.generate_results();
777 script = aRequest.value;
780 script = "let func = function() {" +
783 "func.apply(null, __marionetteParams);";
785 this.executeScriptInSandbox(_chromeSandbox, script, directInject,
786 false, command_id, timeout);
789 this.sendError(e.name + ': ' + e.message, 17, e.stack, command_id);
794 * Set the timeout for asynchronous script execution
796 * @param object aRequest
797 * 'value' member is time in milliseconds to set timeout
799 setScriptTimeout: function MDA_setScriptTimeout(aRequest) {
800 this.command_id = this.getCommandId();
801 let timeout = parseInt(aRequest.value);
803 this.sendError("Not a Number", 500, null, this.command_id);
806 this.scriptTimeout = timeout;
807 this.sendOk(this.command_id);
812 * execute pure JS script. Used to execute 'mochitest'-style Marionette tests.
814 * @param object aRequest
815 * 'value' member holds the script to execute
816 * 'args' member holds the arguments to the script
817 * 'timeout' member will be used as the script timeout if it is given
819 executeJSScript: function MDA_executeJSScript(aRequest) {
820 let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
821 let command_id = this.command_id = this.getCommandId();
822 //all pure JS scripts will need to call Marionette.finish() to complete the test.
823 if (aRequest.newSandbox == undefined) {
824 //if client does not send a value in newSandbox,
825 //then they expect the same behaviour as webdriver
826 aRequest.newSandbox = true;
828 if (this.context == "chrome") {
829 if (aRequest.async) {
830 this.executeWithCallback(aRequest, aRequest.async);
833 this.execute(aRequest, true);
837 this.sendAsync("executeJSScript", { value: aRequest.value,
839 newSandbox: aRequest.newSandbox,
840 async: aRequest.async,
842 command_id: command_id,
843 specialPowers: aRequest.specialPowers });
848 * This function is used by executeAsync and executeJSScript to execute a script
851 * For executeJSScript, it will return a message only when the finish() method is called.
852 * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
853 * method is called, or if it times out.
855 * @param object aRequest
856 * 'value' member holds the script to execute
857 * 'args' member holds the arguments for the script
858 * @param boolean directInject
859 * if true, it will be run directly and not as a
862 executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
863 let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
864 let command_id = this.command_id = this.getCommandId();
865 this.logRequest("executeWithCallback", aRequest);
866 if (aRequest.newSandbox == undefined) {
867 //if client does not send a value in newSandbox,
868 //then they expect the same behaviour as webdriver
869 aRequest.newSandbox = true;
872 if (this.context == "content") {
873 this.sendAsync("executeAsyncScript", {value: aRequest.value,
876 newSandbox: aRequest.newSandbox,
878 command_id: command_id,
879 specialPowers: aRequest.specialPowers});
883 let curWindow = this.getCurrentWindow();
884 let original_onerror = curWindow.onerror;
886 that.timeout = timeout;
887 let marionette = new Marionette(this, curWindow, "chrome",
888 this.marionetteLog, this.marionettePerf,
889 timeout, this.testName);
890 marionette.command_id = this.command_id;
892 function chromeAsyncReturnFunc(value, status) {
893 if (that._emu_cbs && Object.keys(that._emu_cbs).length) {
894 value = "Emulator callback still pending when finish() called";
896 that._emu_cbs = null;
899 if (value == undefined)
901 if (that.command_id == marionette.command_id) {
902 if (that.timer != null) {
907 curWindow.onerror = original_onerror;
909 if (status == 0 || status == undefined) {
910 that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status},
911 marionette.command_id);
914 let error_msg = {message: value, status: status, stacktrace: null};
915 that.sendToClient({from: that.actorID, error: error_msg},
916 marionette.command_id);
921 curWindow.onerror = function (errorMsg, url, lineNumber) {
922 chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17);
926 function chromeAsyncFinish() {
927 chromeAsyncReturnFunc(marionette.generate_results(), 0);
930 let _chromeSandbox = this.createExecuteSandbox(curWindow,
933 aRequest.specialPowers,
940 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
941 if (this.timer != null) {
942 this.timer.initWithCallback(function() {
943 chromeAsyncReturnFunc("timed out", 28);
944 }, that.timeout, Ci.nsITimer.TYPE_ONESHOT);
947 _chromeSandbox.returnFunc = chromeAsyncReturnFunc;
948 _chromeSandbox.finish = chromeAsyncFinish;
952 script = aRequest.value;
955 script = '__marionetteParams.push(returnFunc);'
956 + 'let marionetteScriptFinished = returnFunc;'
957 + 'let __marionetteFunc = function() {' + aRequest.value + '};'
958 + '__marionetteFunc.apply(null, __marionetteParams);';
961 this.executeScriptInSandbox(_chromeSandbox, script, directInject,
962 true, command_id, timeout);
964 chromeAsyncReturnFunc(e.name + ": " + e.message, 17);
969 * Navigates to given url
971 * @param object aRequest
972 * 'value' member holds the url to navigate to
974 goUrl: function MDA_goUrl(aRequest) {
975 let command_id = this.command_id = this.getCommandId();
976 if (this.context != "chrome") {
977 aRequest.command_id = command_id;
978 aRequest.pageTimeout = this.pageTimeout;
979 this.sendAsync("goUrl", aRequest);
983 this.getCurrentWindow().location.href = aRequest.value;
984 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
985 let start = new Date().getTime();
987 function checkLoad() {
988 end = new Date().getTime();
989 let elapse = end - start;
990 if (this.pageTimeout == null || elapse <= this.pageTimeout){
991 if (curWindow.document.readyState == "complete") {
996 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1000 sendError("Error loading page", 13, null, command_id);
1004 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1010 getUrl: function MDA_getUrl() {
1011 this.command_id = this.getCommandId();
1012 if (this.context == "chrome") {
1013 this.sendResponse(this.getCurrentWindow().location.href, this.command_id);
1016 this.sendAsync("getUrl", {command_id: this.command_id});
1021 * Gets the current title of the window
1023 getTitle: function MDA_getTitle() {
1024 this.command_id = this.getCommandId();
1025 this.sendAsync("getTitle", {command_id: this.command_id});
1029 * Gets the page source of the content document
1031 getPageSource: function MDA_getPageSource(){
1032 this.command_id = this.getCommandId();
1033 if (this.context == "chrome"){
1034 var curWindow = this.getCurrentWindow();
1035 var XMLSerializer = curWindow.XMLSerializer;
1036 var pageSource = new XMLSerializer().serializeToString(curWindow.document);
1037 this.sendResponse(pageSource, this.command_id);
1040 this.sendAsync("getPageSource", {command_id: this.command_id});
1045 * Go back in history
1047 goBack: function MDA_goBack() {
1048 this.command_id = this.getCommandId();
1049 this.sendAsync("goBack", {command_id: this.command_id});
1053 * Go forward in history
1055 goForward: function MDA_goForward() {
1056 this.command_id = this.getCommandId();
1057 this.sendAsync("goForward", {command_id: this.command_id});
1063 refresh: function MDA_refresh() {
1064 this.command_id = this.getCommandId();
1065 this.sendAsync("refresh", {command_id: this.command_id});
1069 * Get the current window's server-assigned ID
1071 getWindow: function MDA_getWindow() {
1072 this.command_id = this.getCommandId();
1073 for (let i in this.browsers) {
1074 if (this.curBrowser == this.browsers[i]) {
1075 this.sendResponse(i, this.command_id);
1081 * Get the server-assigned IDs of all available windows
1083 getWindows: function MDA_getWindows() {
1084 this.command_id = this.getCommandId();
1086 let winEn = this.getWinEnumerator();
1087 while(winEn.hasMoreElements()) {
1088 let foundWin = winEn.getNext();
1089 let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
1090 winId = winId + ((appName == "B2G") ? '-b2g' : '');
1093 this.sendResponse(res, this.command_id);
1097 * Switch to a window based on name or server-assigned id.
1098 * Searches based on name, then id.
1100 * @param object aRequest
1101 * 'value' member holds the name or id of the window to switch to
1103 switchToWindow: function MDA_switchToWindow(aRequest) {
1104 let command_id = this.command_id = this.getCommandId();
1105 let winEn = this.getWinEnumerator();
1106 while(winEn.hasMoreElements()) {
1107 let foundWin = winEn.getNext();
1108 let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor)
1109 .getInterface(Ci.nsIDOMWindowUtils)
1111 winId = winId + ((appName == "B2G") ? '-b2g' : '');
1112 if (aRequest.value == foundWin.name || aRequest.value == winId) {
1113 if (this.browsers[winId] == undefined) {
1114 //enable Marionette in that browser window
1115 this.startBrowser(foundWin, false);
1118 utils.window = foundWin;
1119 this.curBrowser = this.browsers[winId];
1121 this.sendOk(command_id);
1125 this.sendError("Unable to locate window " + aRequest.value, 23, null,
1130 * Switch to a given frame within the current window
1132 * @param object aRequest
1133 * 'element' is the element to switch to
1134 * 'value' if element is not set, then this
1135 * holds either the id, name or index
1136 * of the frame to switch to
1138 switchToFrame: function MDA_switchToFrame(aRequest) {
1139 let command_id = this.command_id = this.getCommandId();
1140 this.logRequest("switchToFrame", aRequest);
1141 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1142 let checkLoad = function() {
1143 let errorRegex = /about:.+(error)|(blocked)\?/;
1144 if (curWindow.document.readyState == "complete") {
1145 this.sendOk(command_id);
1148 else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)) {
1149 this.sendError("Error loading page", 13, null, command_id);
1153 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1155 let curWindow = this.getCurrentWindow();
1156 if (this.context == "chrome") {
1157 let foundFrame = null;
1158 if ((aRequest.value == null) && (aRequest.element == null)) {
1159 this.curFrame = null;
1160 if (aRequest.focus) {
1161 this.mainFrame.focus();
1163 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1166 if (aRequest.element != undefined) {
1167 if (this.curBrowser.elementManager.seenItems[aRequest.element] != undefined) {
1168 let wantedFrame = this.curBrowser.elementManager.getKnownElement(aRequest.element, curWindow); //HTMLIFrameElement
1169 let numFrames = curWindow.frames.length;
1170 for (let i = 0; i < numFrames; i++) {
1171 if (curWindow.frames[i].frameElement == wantedFrame) {
1172 curWindow = curWindow.frames[i];
1173 this.curFrame = curWindow;
1174 if (aRequest.focus) {
1175 this.curFrame.focus();
1177 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1183 switch(typeof(aRequest.value)) {
1185 let foundById = null;
1186 let numFrames = curWindow.frames.length;
1187 for (let i = 0; i < numFrames; i++) {
1188 //give precedence to name
1189 let frame = curWindow.frames[i];
1190 let frameElement = frame.frameElement;
1191 if (frame.name == aRequest.value) {
1194 } else if ((foundById == null) && (frameElement.id == aRequest.value)) {
1198 if ((foundFrame == null) && (foundById != null)) {
1199 foundFrame = foundById;
1203 if (curWindow.frames[aRequest.value] != undefined) {
1204 foundFrame = aRequest.value;
1208 if (foundFrame != null) {
1209 curWindow = curWindow.frames[foundFrame];
1210 this.curFrame = curWindow;
1211 if (aRequest.focus) {
1212 this.curFrame.focus();
1214 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1216 this.sendError("Unable to locate frame: " + aRequest.value, 8, null,
1221 if ((!aRequest.value) && (!aRequest.element) &&
1222 (this.currentRemoteFrame !== null)) {
1223 // We're currently using a ChromeMessageSender for a remote frame, so this
1224 // request indicates we need to switch back to the top-level (parent) frame.
1225 // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so
1226 // we send the message to the right listener.
1227 this.switchToGlobalMessageManager();
1229 aRequest.command_id = command_id;
1230 this.sendAsync("switchToFrame", aRequest);
1235 * Set timeout for searching for elements
1237 * @param object aRequest
1238 * 'value' holds the search timeout in milliseconds
1240 setSearchTimeout: function MDA_setSearchTimeout(aRequest) {
1241 this.command_id = this.getCommandId();
1242 if (this.context == "chrome") {
1244 this.curBrowser.elementManager.setSearchTimeout(aRequest.value);
1245 this.sendOk(this.command_id);
1248 this.sendError(e.message, e.code, e.stack, this.command_id);
1252 this.sendAsync("setSearchTimeout", {value: aRequest.value,
1253 command_id: this.command_id});
1258 * Set timeout for page loading, searching and scripts
1260 * @param object aRequest
1261 * 'type' hold the type of timeout
1262 * 'ms' holds the timeout in milliseconds
1264 timeouts: function MDA_timeouts(aRequest){
1266 this.command_id = this.getCommandId();
1267 let timeout_type = aRequest.timeoutType;
1268 let timeout = parseInt(aRequest.ms);
1269 if (isNaN(timeout)) {
1270 this.sendError("Not a Number", 500, null, this.command_id);
1273 if (timeout_type == "implicit") {
1274 aRequest.value = aRequest.ms;
1275 this.setSearchTimeout(aRequest);
1277 else if (timeout_type == "script") {
1278 aRequest.value = aRequest.ms;
1279 this.setScriptTimeout(aRequest);
1282 this.pageTimeout = timeout;
1283 this.sendOk(this.command_id);
1291 * @param object aRequest
1292 'element' represents the ID of the element to single tap on
1294 singleTap: function MDA_singleTap(aRequest) {
1295 this.command_id = this.getCommandId();
1296 let serId = aRequest.element;
1299 if (this.context == "chrome") {
1300 this.sendError("Not in Chrome", 500, null, this.command_id);
1303 this.sendAsync("singleTap", {value: serId,
1306 command_id: this.command_id});
1313 * @param object aRequest
1314 * 'element' represents the ID of the element to double tap on
1316 doubleTap: function MDA_doubleTap(aRequest) {
1317 this.command_id = this.getCommandId();
1318 let serId = aRequest.element;
1321 if (this.context == "chrome") {
1322 this.sendError("Not in Chrome", 500, null, this.command_id);
1325 this.sendAsync("doubleTap", {value: serId,
1328 command_id: this.command_id});
1335 * @param object aRequest
1336 * 'element' represents the ID of the element to touch
1338 press: function MDA_press(aRequest) {
1339 this.command_id = this.getCommandId();
1340 let element = aRequest.element;
1343 if (this.context == "chrome") {
1344 this.sendError("Not in Chrome", 500, null, this.command_id);
1347 this.sendAsync("press", {value: element,
1350 command_id: this.command_id});
1357 * @param object aRequest
1358 * 'element' represents the ID of the element to touch
1360 cancelTouch: function MDA_cancelTouch(aRequest) {
1361 this.command_id = this.getCommandId();
1362 let element = aRequest.element;
1363 let touchId = aRequest.touchId;
1364 if (this.context == "chrome") {
1365 this.sendError("Not in Chrome", 500, null, this.command_id);
1368 this.sendAsync("cancelTouch", {value: element,
1370 command_id: this.command_id});
1377 * @param object aRequest
1378 * 'element' represents the ID of the element to end the touch
1380 release: function MDA_release(aRequest) {
1381 this.command_id = this.getCommandId();
1382 let element = aRequest.element;
1383 let touchId = aRequest.touchId;
1386 if (this.context == "chrome") {
1387 this.sendError("Not in Chrome", 500, null, this.command_id);
1390 this.sendAsync("release", {value: element,
1394 command_id: this.command_id});
1401 * @param object aRequest
1402 * 'value' represents a nested array: inner array represents each event; outer array represents collection of events
1404 actionChain: function MDA_actionChain(aRequest) {
1405 this.command_id = this.getCommandId();
1406 if (this.context == "chrome") {
1407 this.sendError("Not in Chrome", 500, null, this.command_id);
1410 this.sendAsync("actionChain", {chain: aRequest.chain,
1411 nextId: aRequest.nextId,
1412 command_id: this.command_id});
1419 * @param object aRequest
1420 * 'value' represents a nested array: inner array represents each event;
1421 * middle array represents collection of events for each finger
1422 * outer array represents all the fingers
1425 multiAction: function MDA_multiAction(aRequest) {
1426 this.command_id = this.getCommandId();
1427 if (this.context == "chrome") {
1428 this.sendError("Not in Chrome", 500, null, this.command_id);
1431 this.sendAsync("multiAction", {value: aRequest.value,
1432 maxlen: aRequest.max_length,
1433 command_id: this.command_id});
1438 * Find an element using the indicated search strategy.
1440 * @param object aRequest
1441 * 'using' member indicates which search method to use
1442 * 'value' member is the value the client is looking for
1444 findElement: function MDA_findElement(aRequest) {
1445 let command_id = this.command_id = this.getCommandId();
1446 if (this.context == "chrome") {
1449 let on_success = this.sendResponse.bind(this);
1450 let on_error = this.sendError.bind(this);
1451 id = this.curBrowser.elementManager.find(
1452 this.getCurrentWindow(),
1460 this.sendError(e.message, e.code, e.stack, command_id);
1465 this.sendAsync("findElementContent", {value: aRequest.value,
1466 using: aRequest.using,
1467 element: aRequest.element,
1468 command_id: command_id});
1473 * Find elements using the indicated search strategy.
1475 * @param object aRequest
1476 * 'using' member indicates which search method to use
1477 * 'value' member is the value the client is looking for
1479 findElements: function MDA_findElements(aRequest) {
1480 let command_id = this.command_id = this.getCommandId();
1481 if (this.context == "chrome") {
1484 let on_success = this.sendResponse.bind(this);
1485 let on_error = this.sendError.bind(this);
1486 id = this.curBrowser.elementManager.find(this.getCurrentWindow(),
1494 this.sendError(e.message, e.code, e.stack, command_id);
1499 this.sendAsync("findElementsContent", {value: aRequest.value,
1500 using: aRequest.using,
1501 element: aRequest.element,
1502 command_id: command_id});
1507 * Return the active element on the page
1509 getActiveElement: function MDA_getActiveElement(){
1510 let command_id = this.command_id = this.getCommandId();
1511 this.sendAsync("getActiveElement", {command_id: command_id});
1515 * Send click event to element
1517 * @param object aRequest
1518 * 'element' member holds the reference id to
1519 * the element that will be clicked
1521 clickElement: function MDA_clickElement(aRequest) {
1522 let command_id = this.command_id = this.getCommandId();
1523 if (this.context == "chrome") {
1525 //NOTE: click atom fails, fall back to click() action
1526 let el = this.curBrowser.elementManager.getKnownElement(
1527 aRequest.element, this.getCurrentWindow());
1529 this.sendOk(command_id);
1532 this.sendError(e.message, e.code, e.stack, command_id);
1536 // We need to protect against the click causing an OOP frame to close.
1537 // This fires the mozbrowserclose event when it closes so we need to
1538 // listen for it and then just send an error back. The person making the
1539 // call should be aware something isnt right and handle accordingly
1540 let curWindow = this.getCurrentWindow();
1542 this.mozBrowserClose = function() {
1543 curWindow.removeEventListener('mozbrowserclose', self.mozBrowserClose, true);
1544 self.switchToGlobalMessageManager();
1545 self.sendError("The frame closed during the click, recovering to allow further communications", 500, null, command_id);
1547 curWindow.addEventListener('mozbrowserclose', this.mozBrowserClose, true);
1548 this.sendAsync("clickElement", {element: aRequest.element,
1549 command_id: command_id});
1554 * Get a given attribute of an element
1556 * @param object aRequest
1557 * 'element' member holds the reference id to
1558 * the element that will be inspected
1559 * 'name' member holds the name of the attribute to retrieve
1561 getElementAttribute: function MDA_getElementAttribute(aRequest) {
1562 let command_id = this.command_id = this.getCommandId();
1563 if (this.context == "chrome") {
1565 let el = this.curBrowser.elementManager.getKnownElement(
1566 aRequest.element, this.getCurrentWindow());
1567 this.sendResponse(utils.getElementAttribute(el, aRequest.name),
1571 this.sendError(e.message, e.code, e.stack, command_id);
1575 this.sendAsync("getElementAttribute", {element: aRequest.element,
1576 name: aRequest.name,
1577 command_id: command_id});
1582 * Get the text of an element, if any. Includes the text of all child elements.
1584 * @param object aRequest
1585 * 'element' member holds the reference id to
1586 * the element that will be inspected
1588 getElementText: function MDA_getElementText(aRequest) {
1589 let command_id = this.command_id = this.getCommandId();
1590 if (this.context == "chrome") {
1591 //Note: for chrome, we look at text nodes, and any node with a "label" field
1593 let el = this.curBrowser.elementManager.getKnownElement(
1594 aRequest.element, this.getCurrentWindow());
1596 this.getVisibleText(el, lines);
1597 lines = lines.join("\n");
1598 this.sendResponse(lines, command_id);
1601 this.sendError(e.message, e.code, e.stack, command_id);
1605 this.sendAsync("getElementText", {element: aRequest.element,
1606 command_id: command_id});
1611 * Get the tag name of the element.
1613 * @param object aRequest
1614 * 'element' member holds the reference id to
1615 * the element that will be inspected
1617 getElementTagName: function MDA_getElementTagName(aRequest) {
1618 let command_id = this.command_id = this.getCommandId();
1619 if (this.context == "chrome") {
1621 let el = this.curBrowser.elementManager.getKnownElement(
1622 aRequest.element, this.getCurrentWindow());
1623 this.sendResponse(el.tagName.toLowerCase(), command_id);
1626 this.sendError(e.message, e.code, e.stack, command_id);
1630 this.sendAsync("getElementTagName", {element: aRequest.element,
1631 command_id: command_id});
1636 * Check if element is displayed
1638 * @param object aRequest
1639 * 'element' member holds the reference id to
1640 * the element that will be checked
1642 isElementDisplayed: function MDA_isElementDisplayed(aRequest) {
1643 let command_id = this.command_id = this.getCommandId();
1644 if (this.context == "chrome") {
1646 let el = this.curBrowser.elementManager.getKnownElement(
1647 aRequest.element, this.getCurrentWindow());
1648 this.sendResponse(utils.isElementDisplayed(el), command_id);
1651 this.sendError(e.message, e.code, e.stack, command_id);
1655 this.sendAsync("isElementDisplayed", {element:aRequest.element,
1656 command_id: command_id});
1661 * Check if element is enabled
1663 * @param object aRequest
1664 * 'element' member holds the reference id to
1665 * the element that will be checked
1667 isElementEnabled: function MDA_isElementEnabled(aRequest) {
1668 let command_id = this.command_id = this.getCommandId();
1669 if (this.context == "chrome") {
1671 //Selenium atom doesn't quite work here
1672 let el = this.curBrowser.elementManager.getKnownElement(
1673 aRequest.element, this.getCurrentWindow());
1674 if (el.disabled != undefined) {
1675 this.sendResponse(!!!el.disabled, command_id);
1678 this.sendResponse(true, command_id);
1682 this.sendError(e.message, e.code, e.stack, command_id);
1686 this.sendAsync("isElementEnabled", {element:aRequest.element,
1687 command_id: command_id});
1692 * Check if element is selected
1694 * @param object aRequest
1695 * 'element' member holds the reference id to
1696 * the element that will be checked
1698 isElementSelected: function MDA_isElementSelected(aRequest) {
1699 let command_id = this.command_id = this.getCommandId();
1700 if (this.context == "chrome") {
1702 //Selenium atom doesn't quite work here
1703 let el = this.curBrowser.elementManager.getKnownElement(
1704 aRequest.element, this.getCurrentWindow());
1705 if (el.checked != undefined) {
1706 this.sendResponse(!!el.checked, command_id);
1708 else if (el.selected != undefined) {
1709 this.sendResponse(!!el.selected, command_id);
1712 this.sendResponse(true, command_id);
1716 this.sendError(e.message, e.code, e.stack, command_id);
1720 this.sendAsync("isElementSelected", {element:aRequest.element,
1721 command_id: command_id});
1725 getElementSize: function MDA_getElementSize(aRequest) {
1726 let command_id = this.command_id = this.getCommandId();
1727 if (this.context == "chrome") {
1729 let el = this.curBrowser.elementManager.getKnownElement(
1730 aRequest.element, this.getCurrentWindow());
1731 let clientRect = el.getBoundingClientRect();
1732 this.sendResponse({width: clientRect.width, height: clientRect.height},
1736 this.sendError(e.message, e.code, e.stack, command_id);
1740 this.sendAsync("getElementSize", {element:aRequest.element,
1741 command_id: command_id});
1746 * Send key presses to element after focusing on it
1748 * @param object aRequest
1749 * 'element' member holds the reference id to
1750 * the element that will be checked
1751 * 'value' member holds the value to send to the element
1753 sendKeysToElement: function MDA_sendKeysToElement(aRequest) {
1754 let command_id = this.command_id = this.getCommandId();
1755 if (this.context == "chrome") {
1757 let el = this.curBrowser.elementManager.getKnownElement(
1758 aRequest.element, this.getCurrentWindow());
1760 utils.sendString(aRequest.value.join(""), utils.window);
1761 this.sendOk(command_id);
1764 this.sendError(e.message, e.code, e.stack, command_id);
1768 this.sendAsync("sendKeysToElement", {element:aRequest.element,
1769 value: aRequest.value,
1770 command_id: command_id});
1775 * Sets the test name
1777 * The test name is used in logging messages.
1779 setTestName: function MDA_setTestName(aRequest) {
1780 this.command_id = this.getCommandId();
1781 this.logRequest("setTestName", aRequest);
1782 this.testName = aRequest.value;
1783 this.sendAsync("setTestName", {value: aRequest.value,
1784 command_id: this.command_id});
1788 * Clear the text of an element
1790 * @param object aRequest
1791 * 'element' member holds the reference id to
1792 * the element that will be cleared
1794 clearElement: function MDA_clearElement(aRequest) {
1795 let command_id = this.command_id = this.getCommandId();
1796 if (this.context == "chrome") {
1797 //the selenium atom doesn't work here
1799 let el = this.curBrowser.elementManager.getKnownElement(
1800 aRequest.element, this.getCurrentWindow());
1801 if (el.nodeName == "textbox") {
1804 else if (el.nodeName == "checkbox") {
1807 this.sendOk(command_id);
1810 this.sendError(e.message, e.code, e.stack, command_id);
1814 this.sendAsync("clearElement", {element:aRequest.element,
1815 command_id: command_id});
1819 getElementPosition: function MDA_getElementPosition(aRequest) {
1820 this.command_id = this.getCommandId();
1821 this.sendAsync("getElementPosition", {element:aRequest.element,
1822 command_id: this.command_id});
1826 * Add a cookie to the document.
1828 addCookie: function MDA_addCookie(aRequest) {
1829 this.command_id = this.getCommandId();
1830 this.sendAsync("addCookie", {cookie:aRequest.cookie,
1831 command_id: this.command_id});
1835 * Get all visible cookies for a document
1837 getAllCookies: function MDA_getAllCookies() {
1838 this.command_id = this.getCommandId();
1839 this.sendAsync("getAllCookies", {command_id: this.command_id});
1843 * Delete all cookies that are visible to a document
1845 deleteAllCookies: function MDA_deleteAllCookies() {
1846 this.command_id = this.getCommandId();
1847 this.sendAsync("deleteAllCookies", {command_id: this.command_id});
1851 * Delete a cookie by name
1853 deleteCookie: function MDA_deleteCookie(aRequest) {
1854 this.command_id = this.getCommandId();
1855 this.sendAsync("deleteCookie", {name:aRequest.name,
1856 command_id: this.command_id});
1860 * Closes the Browser Window.
1862 * If it is B2G it returns straight away and does not do anything
1864 * If is desktop it calculates how many windows are open and if there is only
1865 * 1 then it deletes the session otherwise it closes the window
1867 closeWindow: function MDA_closeWindow() {
1868 let command_id = this.command_id = this.getCommandId();
1869 if (appName == "B2G") {
1870 // We can't close windows so just return
1871 this.sendOk(command_id);
1874 // Get the total number of windows
1875 let numOpenWindows = 0;
1876 let winEnum = this.getWinEnumerator();
1877 while (winEnum.hasMoreElements()) {
1878 numOpenWindows += 1;
1882 // if there is only 1 window left, delete the session
1883 if (numOpenWindows === 1){
1884 this.deleteSession();
1889 this.messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
1890 this.getCurrentWindow().close();
1891 this.sendOk(command_id);
1894 this.sendError("Could not close window: " + e.message, 13, e.stack,
1901 * Deletes the session.
1903 * If it is a desktop environment, it will close the session's tab and close all listeners
1905 * If it is a B2G environment, it will make the main content listener sleep, and close
1906 * all other listeners. The main content listener persists after disconnect (it's the homescreen),
1907 * and can safely be reused.
1909 deleteSession: function MDA_deleteSession() {
1910 let command_id = this.command_id = this.getCommandId();
1911 if (this.curBrowser != null) {
1912 if (appName == "B2G") {
1913 this.globalMessageManager.broadcastAsyncMessage(
1914 "Marionette:sleepSession" + this.curBrowser.mainContentId, {});
1915 this.curBrowser.knownFrames.splice(
1916 this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1);
1919 //don't set this pref for B2G since the framescript can be safely reused
1920 Services.prefs.setBoolPref("marionette.contentListener", false);
1922 this.curBrowser.closeTab();
1923 //delete session in each frame in each browser
1924 for (let win in this.browsers) {
1925 for (let i in this.browsers[win].knownFrames) {
1926 this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
1929 let winEnum = this.getWinEnumerator();
1930 while (winEnum.hasMoreElements()) {
1931 winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
1934 this.sendOk(command_id);
1935 this.removeMessageManagerListeners(this.globalMessageManager);
1936 this.switchToGlobalMessageManager();
1937 // reset frame to the top-most frame
1938 this.curFrame = null;
1939 if (this.mainFrame) {
1940 this.mainFrame.focus();
1942 this.curBrowser = null;
1944 this.importedScripts.remove(false);
1951 * Returns the current status of the Application Cache
1953 getAppCacheStatus: function MDA_getAppCacheStatus(aRequest) {
1954 this.command_id = this.getCommandId();
1955 this.sendAsync("getAppCacheStatus", {command_id: this.command_id});
1960 runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
1962 if (!this._emu_cbs) {
1965 this._emu_cbs[this._emu_cb_id] = callback;
1967 this.sendToClient({emulator_cmd: cmd, id: this._emu_cb_id}, -1);
1968 this._emu_cb_id += 1;
1971 emulatorCmdResult: function emulatorCmdResult(message) {
1972 if (this.context != "chrome") {
1973 this.sendAsync("emulatorCmdResult", message);
1977 if (!this._emu_cbs) {
1981 let cb = this._emu_cbs[message.id];
1982 delete this._emu_cbs[message.id];
1990 this.sendError(e.message, e.code, e.stack, -1);
1995 importScript: function MDA_importScript(aRequest) {
1996 let command_id = this.command_id = this.getCommandId();
1997 if (this.context == "chrome") {
1999 if (this.importedScripts.exists()) {
2000 file = FileUtils.openFileOutputStream(this.importedScripts,
2001 FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY);
2004 //Note: The permission bits here don't actually get set (bug 804563)
2005 this.importedScripts.createUnique(
2006 Components.interfaces.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
2007 file = FileUtils.openFileOutputStream(this.importedScripts,
2008 FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
2009 this.importedScripts.permissions = parseInt("0666", 8); //actually set permissions
2011 file.write(aRequest.script, aRequest.script.length);
2013 this.sendOk(command_id);
2016 this.sendAsync("importScript", {script: aRequest.script,
2017 command_id: command_id});
2022 * Takes a screenshot of a DOM node. If there is no node given a screenshot
2023 * of the window will be taken.
2025 screenShot: function MDA_saveScreenshot(aRequest) {
2026 this.command_id = this.getCommandId();
2027 this.sendAsync("screenShot", {element: aRequest.element,
2028 highlights: aRequest.highlights,
2029 command_id: this.command_id});
2033 * Helper function to convert an outerWindowID into a UID that Marionette
2036 generateFrameId: function MDA_generateFrameId(id) {
2037 let uid = id + (appName == "B2G" ? "-b2g" : "");
2042 * Receives all messages from content messageManager
2044 receiveMessage: function MDA_receiveMessage(message) {
2045 // We need to just check if we need to remove the mozbrowserclose listener
2046 if (this.mozBrowserClose !== null){
2047 let curWindow = this.getCurrentWindow();
2048 curWindow.removeEventListener('mozbrowserclose', this.mozBrowserClose, true);
2049 this.mozBrowserClose = null;
2052 switch (message.name) {
2053 case "Marionette:done":
2054 this.sendResponse(message.json.value, message.json.command_id);
2056 case "Marionette:ok":
2057 this.sendOk(message.json.command_id);
2059 case "Marionette:error":
2060 this.sendError(message.json.message, message.json.status, message.json.stacktrace, message.json.command_id);
2062 case "Marionette:log":
2063 //log server-side messages
2064 logger.info(message.json.message);
2066 case "Marionette:shareData":
2067 //log messages from tests
2068 if (message.json.log) {
2069 this.marionetteLog.addLogs(message.json.log);
2071 if (message.json.perf) {
2072 this.marionettePerf.appendPerfData(message.json.perf);
2075 case "Marionette:runEmulatorCmd":
2076 this.sendToClient(message.json, -1);
2078 case "Marionette:switchToFrame":
2079 // Switch to a remote frame.
2080 let thisWin = this.getCurrentWindow();
2081 let frameWindow = thisWin.QueryInterface(Ci.nsIInterfaceRequestor)
2082 .getInterface(Ci.nsIDOMWindowUtils)
2083 .getOuterWindowWithId(message.json.win);
2084 let thisFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame];
2085 let mm = thisFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
2087 // See if this frame already has our frame script loaded in it; if so,
2089 for (let i = 0; i < remoteFrames.length; i++) {
2090 let frame = remoteFrames[i];
2091 if ((frame.messageManager == mm)) {
2092 this.currentRemoteFrame = frame;
2093 this.currentRemoteFrame.command_id = message.json.command_id;
2094 this.messageManager = frame.messageManager;
2095 this.addMessageManagerListeners(this.messageManager);
2096 this.messageManager.sendAsyncMessage("Marionette:restart", {});
2101 // Load the frame script in this frame, and set the frame's ChromeMessageSender
2102 // as the active message manager.
2103 this.addMessageManagerListeners(mm);
2104 mm.loadFrameScript(FRAME_SCRIPT, true);
2105 this.messageManager = mm;
2106 let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame);
2107 aFrame.messageManager = this.messageManager;
2108 aFrame.command_id = message.json.command_id;
2109 remoteFrames.push(aFrame);
2110 this.currentRemoteFrame = aFrame;
2112 case "Marionette:register":
2113 // This code processes the content listener's registration information
2114 // and either accepts the listener, or ignores it
2115 let nullPrevious = (this.curBrowser.curFrameId == null);
2116 let curWin = this.getCurrentWindow();
2117 let listenerWindow = curWin.QueryInterface(Ci.nsIInterfaceRequestor)
2118 .getInterface(Ci.nsIDOMWindowUtils)
2119 .getOuterWindowWithId(message.json.value);
2121 if (!listenerWindow || (listenerWindow.location.href != message.json.href) &&
2122 (this.currentRemoteFrame !== null)) {
2123 // The outerWindowID from an OOP frame will not be meaningful to
2124 // the parent process here, since each process maintains its own
2125 // independent window list. So, it will either be null (!listenerWindow)
2126 // or it will point to some random window, which will hopefully
2127 // cause an href mistmach. Currently this only happens
2128 // in B2G for OOP frames registered in Marionette:switchToFrame, so
2129 // we'll acknowledge the switchToFrame message here.
2130 // XXX: Should have a better way of determining that this message
2131 // is from a remote frame.
2132 this.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value);
2135 {scriptTimeout: this.scriptTimeout,
2136 searchTimeout: this.curBrowser.elementManager.searchTimeout,
2137 command_id: this.currentRemoteFrame.command_id});
2142 browserType = message.target.getAttribute("type");
2144 // browserType remains undefined.
2147 if (!browserType || browserType != "content") {
2148 reg.id = this.curBrowser.register(this.generateFrameId(message.json.value),
2151 this.curBrowser.elementManager.seenItems[reg.id] = listenerWindow; //add to seenItems
2152 reg.importedScripts = this.importedScripts.path;
2153 if (nullPrevious && (this.curBrowser.curFrameId != null)) {
2154 this.sendAsync("newSession", {B2G: (appName == "B2G")});
2155 if (this.curBrowser.newSession) {
2156 this.sendResponse(reg.id, this.newSessionCommandId);
2157 this.newSessionCommandId = null;
2165 MarionetteDriverActor.prototype.requestTypes = {
2166 "newSession": MarionetteDriverActor.prototype.newSession,
2167 "getSessionCapabilities": MarionetteDriverActor.prototype.getSessionCapabilities,
2168 "getStatus": MarionetteDriverActor.prototype.getStatus,
2169 "log": MarionetteDriverActor.prototype.log,
2170 "getLogs": MarionetteDriverActor.prototype.getLogs,
2171 "addPerfData": MarionetteDriverActor.prototype.addPerfData,
2172 "getPerfData": MarionetteDriverActor.prototype.getPerfData,
2173 "setContext": MarionetteDriverActor.prototype.setContext,
2174 "executeScript": MarionetteDriverActor.prototype.execute,
2175 "setScriptTimeout": MarionetteDriverActor.prototype.setScriptTimeout,
2176 "timeouts": MarionetteDriverActor.prototype.timeouts,
2177 "singleTap": MarionetteDriverActor.prototype.singleTap,
2178 "doubleTap": MarionetteDriverActor.prototype.doubleTap,
2179 "press": MarionetteDriverActor.prototype.press,
2180 "release": MarionetteDriverActor.prototype.release,
2181 "cancelTouch": MarionetteDriverActor.prototype.cancelTouch,
2182 "actionChain": MarionetteDriverActor.prototype.actionChain,
2183 "multiAction": MarionetteDriverActor.prototype.multiAction,
2184 "executeAsyncScript": MarionetteDriverActor.prototype.executeWithCallback,
2185 "executeJSScript": MarionetteDriverActor.prototype.executeJSScript,
2186 "setSearchTimeout": MarionetteDriverActor.prototype.setSearchTimeout,
2187 "findElement": MarionetteDriverActor.prototype.findElement,
2188 "findElements": MarionetteDriverActor.prototype.findElements,
2189 "clickElement": MarionetteDriverActor.prototype.clickElement,
2190 "getElementAttribute": MarionetteDriverActor.prototype.getElementAttribute,
2191 "getElementText": MarionetteDriverActor.prototype.getElementText,
2192 "getElementTagName": MarionetteDriverActor.prototype.getElementTagName,
2193 "isElementDisplayed": MarionetteDriverActor.prototype.isElementDisplayed,
2194 "getElementSize": MarionetteDriverActor.prototype.getElementSize,
2195 "isElementEnabled": MarionetteDriverActor.prototype.isElementEnabled,
2196 "isElementSelected": MarionetteDriverActor.prototype.isElementSelected,
2197 "sendKeysToElement": MarionetteDriverActor.prototype.sendKeysToElement,
2198 "getElementPosition": MarionetteDriverActor.prototype.getElementPosition,
2199 "clearElement": MarionetteDriverActor.prototype.clearElement,
2200 "getTitle": MarionetteDriverActor.prototype.getTitle,
2201 "getPageSource": MarionetteDriverActor.prototype.getPageSource,
2202 "goUrl": MarionetteDriverActor.prototype.goUrl,
2203 "getUrl": MarionetteDriverActor.prototype.getUrl,
2204 "goBack": MarionetteDriverActor.prototype.goBack,
2205 "goForward": MarionetteDriverActor.prototype.goForward,
2206 "refresh": MarionetteDriverActor.prototype.refresh,
2207 "getWindow": MarionetteDriverActor.prototype.getWindow,
2208 "getWindows": MarionetteDriverActor.prototype.getWindows,
2209 "switchToFrame": MarionetteDriverActor.prototype.switchToFrame,
2210 "switchToWindow": MarionetteDriverActor.prototype.switchToWindow,
2211 "deleteSession": MarionetteDriverActor.prototype.deleteSession,
2212 "emulatorCmdResult": MarionetteDriverActor.prototype.emulatorCmdResult,
2213 "importScript": MarionetteDriverActor.prototype.importScript,
2214 "getAppCacheStatus": MarionetteDriverActor.prototype.getAppCacheStatus,
2215 "closeWindow": MarionetteDriverActor.prototype.closeWindow,
2216 "setTestName": MarionetteDriverActor.prototype.setTestName,
2217 "screenShot": MarionetteDriverActor.prototype.screenShot,
2218 "addCookie": MarionetteDriverActor.prototype.addCookie,
2219 "getAllCookies": MarionetteDriverActor.prototype.getAllCookies,
2220 "deleteAllCookies": MarionetteDriverActor.prototype.deleteAllCookies,
2221 "deleteCookie": MarionetteDriverActor.prototype.deleteCookie,
2222 "getActiveElement": MarionetteDriverActor.prototype.getActiveElement
2226 * Creates a BrowserObj. BrowserObjs handle interactions with the
2227 * browser, according to the current environment (desktop, b2g, etc.)
2229 * @param nsIDOMWindow win
2230 * The window whose browser needs to be accessed
2233 function BrowserObj(win) {
2234 this.DESKTOP = "desktop";
2239 this.knownFrames = [];
2240 this.curFrameId = null;
2241 this.startPage = "about:blank";
2242 this.mainContentId = null; // used in B2G to identify the homescreen content page
2243 this.newSession = true; //used to set curFrameId upon new session
2244 this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
2245 this.setBrowser(win);
2248 BrowserObj.prototype = {
2250 * Set the browser if the application is not B2G
2252 * @param nsIDOMWindow win
2253 * current window reference
2255 setBrowser: function BO_setBrowser(win) {
2258 this.browser = win.gBrowser;
2261 this.browser = win.BrowserApp;
2266 * Called when we start a session with this browser.
2268 * In a desktop environment, if newTab is true, it will start
2269 * a new 'about:blank' tab and change focus to this tab.
2271 * This will also set the active messagemanager for this object
2273 * @param boolean newTab
2274 * If true, create new tab
2276 startSession: function BO_startSession(newTab, win, callback) {
2277 if (appName != "Firefox") {
2278 callback(win, newTab);
2281 this.addTab(this.startPage);
2282 //if we have a new tab, make it the selected tab
2283 this.browser.selectedTab = this.tab;
2284 let newTabBrowser = this.browser.getBrowserForTab(this.tab);
2285 // wait for tab to be loaded
2286 newTabBrowser.addEventListener("load", function onLoad() {
2287 newTabBrowser.removeEventListener("load", onLoad, true);
2288 callback(win, newTab);
2292 //set this.tab to the currently focused tab
2293 if (this.browser != undefined && this.browser.selectedTab != undefined) {
2294 this.tab = this.browser.selectedTab;
2296 callback(win, newTab);
2301 * Closes current tab
2303 closeTab: function BO_closeTab() {
2304 if (this.tab != null && (appName != "B2G")) {
2305 this.browser.removeTab(this.tab);
2311 * Opens a tab with given uri
2316 addTab: function BO_addTab(uri) {
2317 this.tab = this.browser.addTab(uri, true);
2321 * Loads content listeners if we don't already have them
2323 * @param string script
2324 * path of script to load
2325 * @param nsIDOMWindow frame
2326 * frame to load the script in
2328 loadFrameScript: function BO_loadFrameScript(script, frame) {
2329 frame.window.messageManager.loadFrameScript(script, true);
2330 Services.prefs.setBoolPref("marionette.contentListener", true);
2334 * Registers a new frame, and sets its current frame id to this frame
2335 * if it is not already assigned, and if a) we already have a session
2336 * or b) we're starting a new session and it is the right start frame.
2340 * @param string href
2343 register: function BO_register(uid, href) {
2344 if (this.curFrameId == null) {
2345 if ((!this.newSession) || (this.newSession &&
2346 ((appName != "Firefox") || href.indexOf(this.startPage) > -1))) {
2347 this.curFrameId = uid;
2348 this.mainContentId = uid;
2351 this.knownFrames.push(uid); //used to delete sessions