michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js"; michael@0: michael@0: // import logger michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: let logger = Log.repository.getLogger("Marionette"); michael@0: logger.info('marionette-server.js loaded'); michael@0: michael@0: let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] michael@0: .getService(Ci.mozIJSSubScriptLoader); michael@0: loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js"); michael@0: loader.loadSubScript("chrome://marionette/content/marionette-common.js"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: loader.loadSubScript("chrome://marionette/content/marionette-frame-manager.js"); michael@0: Cu.import("chrome://marionette/content/marionette-elements.js"); michael@0: let utils = {}; michael@0: loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils); michael@0: loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils); michael@0: loader.loadSubScript("chrome://marionette/content/atoms.js", utils); michael@0: michael@0: let specialpowers = {}; michael@0: loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", michael@0: specialpowers); michael@0: specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver(); michael@0: specialpowers.specialPowersObserver.init(); michael@0: michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: Services.prefs.setBoolPref("marionette.contentListener", false); michael@0: let appName = Services.appinfo.name; michael@0: michael@0: let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: let DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js"); michael@0: this.DevToolsUtils = DevToolsUtils; michael@0: loader.loadSubScript("resource://gre/modules/devtools/server/transport.js"); michael@0: michael@0: let bypassOffline = false; michael@0: let qemu = "0"; michael@0: let device = null; michael@0: michael@0: try { michael@0: XPCOMUtils.defineLazyGetter(this, "libcutils", function () { michael@0: Cu.import("resource://gre/modules/systemlibs.js"); michael@0: return libcutils; michael@0: }); michael@0: if (libcutils) { michael@0: qemu = libcutils.property_get("ro.kernel.qemu"); michael@0: logger.info("B2G emulator: " + (qemu == "1" ? "yes" : "no")); michael@0: device = libcutils.property_get("ro.product.device"); michael@0: logger.info("Device detected is " + device); michael@0: bypassOffline = (qemu == "1" || device == "panda"); michael@0: } michael@0: } michael@0: catch(e) {} michael@0: michael@0: if (bypassOffline) { michael@0: logger.info("Bypassing offline status."); michael@0: Services.prefs.setBoolPref("network.gonk.manage-offline-status", false); michael@0: Services.io.manageOfflineStatus = false; michael@0: Services.io.offline = false; michael@0: } michael@0: michael@0: // This is used to prevent newSession from returning before the telephony michael@0: // API's are ready; see bug 792647. This assumes that marionette-server.js michael@0: // will be loaded before the 'system-message-listener-ready' message michael@0: // is fired. If this stops being true, this approach will have to change. michael@0: let systemMessageListenerReady = false; michael@0: Services.obs.addObserver(function() { michael@0: systemMessageListenerReady = true; michael@0: }, "system-message-listener-ready", false); michael@0: michael@0: /* michael@0: * Custom exceptions michael@0: */ michael@0: function FrameSendNotInitializedError(frame) { michael@0: this.code = 54; michael@0: this.frame = frame; michael@0: this.message = "Error sending message to frame (NS_ERROR_NOT_INITIALIZED)"; michael@0: this.toString = function() { michael@0: return this.message + " " + this.frame + "; frame has closed."; michael@0: } michael@0: } michael@0: michael@0: function FrameSendFailureError(frame) { michael@0: this.code = 55; michael@0: this.frame = frame; michael@0: this.message = "Error sending message to frame (NS_ERROR_FAILURE)"; michael@0: this.toString = function() { michael@0: return this.message + " " + this.frame + "; frame not responding."; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * The server connection is responsible for all marionette API calls. It gets created michael@0: * for each connection and manages all chrome and browser based calls. It michael@0: * mediates content calls by issuing appropriate messages to the content process. michael@0: */ michael@0: function MarionetteServerConnection(aPrefix, aTransport, aServer) michael@0: { michael@0: this.uuidGen = Cc["@mozilla.org/uuid-generator;1"] michael@0: .getService(Ci.nsIUUIDGenerator); michael@0: michael@0: this.prefix = aPrefix; michael@0: this.server = aServer; michael@0: this.conn = aTransport; michael@0: this.conn.hooks = this; michael@0: michael@0: // marionette uses a protocol based on the debugger server, which requires michael@0: // passing back "actor ids" with responses. unlike the debugger server, michael@0: // we don't have multiple actors, so just use a dummy value of "0" here michael@0: this.actorID = "0"; michael@0: michael@0: this.globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"] michael@0: .getService(Ci.nsIMessageBroadcaster); michael@0: this.messageManager = this.globalMessageManager; michael@0: this.browsers = {}; //holds list of BrowserObjs michael@0: this.curBrowser = null; // points to current browser michael@0: this.context = "content"; michael@0: this.scriptTimeout = null; michael@0: this.searchTimeout = null; michael@0: this.pageTimeout = null; michael@0: this.timer = null; michael@0: this.inactivityTimer = null; michael@0: this.heartbeatCallback = function () {}; // called by simpletest methods michael@0: this.marionetteLog = new MarionetteLogObj(); michael@0: this.command_id = null; michael@0: this.mainFrame = null; //topmost chrome frame michael@0: this.curFrame = null; // chrome iframe that currently has focus michael@0: this.mainContentFrameId = null; michael@0: this.importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']); michael@0: this.importedScriptHashes = {"chrome" : [], "content": []}; michael@0: this.currentFrameElement = null; michael@0: this.testName = null; michael@0: this.mozBrowserClose = null; michael@0: } michael@0: michael@0: MarionetteServerConnection.prototype = { michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener, michael@0: Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference]), michael@0: michael@0: /** michael@0: * Debugger transport callbacks: michael@0: */ michael@0: onPacket: function MSC_onPacket(aPacket) { michael@0: // Dispatch the request michael@0: if (this.requestTypes && this.requestTypes[aPacket.name]) { michael@0: try { michael@0: this.requestTypes[aPacket.name].bind(this)(aPacket); michael@0: } catch(e) { michael@0: this.conn.send({ error: ("error occurred while processing '" + michael@0: aPacket.name), michael@0: message: e.message }); michael@0: } michael@0: } else { michael@0: this.conn.send({ error: "unrecognizedPacketType", michael@0: message: ('Marionette does not ' + michael@0: 'recognize the packet type "' + michael@0: aPacket.name + '"') }); michael@0: } michael@0: }, michael@0: michael@0: onClosed: function MSC_onClosed(aStatus) { michael@0: this.server._connectionClosed(this); michael@0: this.sessionTearDown(); michael@0: }, michael@0: michael@0: /** michael@0: * Helper methods: michael@0: */ michael@0: michael@0: /** michael@0: * Switches to the global ChromeMessageBroadcaster, potentially replacing a frame-specific michael@0: * ChromeMessageSender. Has no effect if the global ChromeMessageBroadcaster is already michael@0: * in use. If this replaces a frame-specific ChromeMessageSender, it removes the message michael@0: * listeners from that sender, and then puts the corresponding frame script "to sleep", michael@0: * which removes most of the message listeners from it as well. michael@0: */ michael@0: switchToGlobalMessageManager: function MDA_switchToGlobalMM() { michael@0: if (this.curBrowser && this.curBrowser.frameManager.currentRemoteFrame !== null) { michael@0: this.curBrowser.frameManager.removeMessageManagerListeners(this.messageManager); michael@0: this.sendAsync("sleepSession", null, null, true); michael@0: this.curBrowser.frameManager.currentRemoteFrame = null; michael@0: } michael@0: this.messageManager = this.globalMessageManager; michael@0: }, michael@0: michael@0: /** michael@0: * Helper method to send async messages to the content listener michael@0: * michael@0: * @param string name michael@0: * Suffix of the targetted message listener (Marionette:) michael@0: * @param object values michael@0: * Object to send to the listener michael@0: */ michael@0: sendAsync: function MDA_sendAsync(name, values, commandId, ignoreFailure) { michael@0: let success = true; michael@0: if (values instanceof Object && commandId) { michael@0: values.command_id = commandId; michael@0: } michael@0: if (this.curBrowser.frameManager.currentRemoteFrame !== null) { michael@0: try { michael@0: this.messageManager.sendAsyncMessage( michael@0: "Marionette:" + name + this.curBrowser.frameManager.currentRemoteFrame.targetFrameId, values); michael@0: } michael@0: catch(e) { michael@0: if (!ignoreFailure) { michael@0: success = false; michael@0: let error = e; michael@0: switch(e.result) { michael@0: case Components.results.NS_ERROR_FAILURE: michael@0: error = new FrameSendFailureError(this.curBrowser.frameManager.currentRemoteFrame); michael@0: break; michael@0: case Components.results.NS_ERROR_NOT_INITIALIZED: michael@0: error = new FrameSendNotInitializedError(this.curBrowser.frameManager.currentRemoteFrame); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: let code = error.hasOwnProperty('code') ? e.code : 500; michael@0: this.sendError(error.toString(), code, error.stack, commandId); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: this.messageManager.broadcastAsyncMessage( michael@0: "Marionette:" + name + this.curBrowser.curFrameId, values); michael@0: } michael@0: return success; michael@0: }, michael@0: michael@0: logRequest: function MDA_logRequest(type, data) { michael@0: logger.debug("Got request: " + type + ", data: " + JSON.stringify(data) + ", id: " + this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Generic method to pass a response to the client michael@0: * michael@0: * @param object msg michael@0: * Response to send back to client michael@0: * @param string command_id michael@0: * Unique identifier assigned to the client's request. michael@0: * Used to distinguish the asynchronous responses. michael@0: */ michael@0: sendToClient: function MDA_sendToClient(msg, command_id) { michael@0: logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id + michael@0: ", " + this.command_id); michael@0: if (!command_id) { michael@0: logger.warn("got a response with no command_id"); michael@0: return; michael@0: } michael@0: else if (command_id != -1) { michael@0: // A command_id of -1 is used for emulator callbacks, and those michael@0: // don't use this.command_id. michael@0: if (!this.command_id) { michael@0: // A null value for this.command_id means we've already processed michael@0: // a message for the previous value, and so the current message is a michael@0: // duplicate. michael@0: logger.warn("ignoring duplicate response for command_id " + command_id); michael@0: return; michael@0: } michael@0: else if (this.command_id != command_id) { michael@0: logger.warn("ignoring out-of-sync response"); michael@0: return; michael@0: } michael@0: } michael@0: this.conn.send(msg); michael@0: if (command_id != -1) { michael@0: // Don't unset this.command_id if this message is to process an michael@0: // emulator callback, since another response for this command_id is michael@0: // expected, after the containing call to execute_async_script finishes. michael@0: this.command_id = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Send a value to client michael@0: * michael@0: * @param object value michael@0: * Value to send back to client michael@0: * @param string command_id michael@0: * Unique identifier assigned to the client's request. michael@0: * Used to distinguish the asynchronous responses. michael@0: */ michael@0: sendResponse: function MDA_sendResponse(value, command_id) { michael@0: if (typeof(value) == 'undefined') michael@0: value = null; michael@0: this.sendToClient({from:this.actorID, value: value}, command_id); michael@0: }, michael@0: michael@0: sayHello: function MDA_sayHello() { michael@0: this.conn.send({ from: "root", michael@0: applicationType: "gecko", michael@0: traits: [] }); michael@0: }, michael@0: michael@0: getMarionetteID: function MDA_getMarionette() { michael@0: this.conn.send({ "from": "root", "id": this.actorID }); michael@0: }, michael@0: michael@0: /** michael@0: * Send ack to client michael@0: * michael@0: * @param string command_id michael@0: * Unique identifier assigned to the client's request. michael@0: * Used to distinguish the asynchronous responses. michael@0: */ michael@0: sendOk: function MDA_sendOk(command_id) { michael@0: this.sendToClient({from:this.actorID, ok: true}, command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Send error message to client michael@0: * michael@0: * @param string message michael@0: * Error message michael@0: * @param number status michael@0: * Status number michael@0: * @param string trace michael@0: * Stack trace michael@0: * @param string command_id michael@0: * Unique identifier assigned to the client's request. michael@0: * Used to distinguish the asynchronous responses. michael@0: */ michael@0: sendError: function MDA_sendError(message, status, trace, command_id) { michael@0: let error_msg = {message: message, status: status, stacktrace: trace}; michael@0: this.sendToClient({from:this.actorID, error: error_msg}, command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the current active window michael@0: * michael@0: * @return nsIDOMWindow michael@0: */ michael@0: getCurrentWindow: function MDA_getCurrentWindow() { michael@0: let type = null; michael@0: if (this.curFrame == null) { michael@0: if (this.curBrowser == null) { michael@0: if (this.context == "content") { michael@0: type = 'navigator:browser'; michael@0: } michael@0: return Services.wm.getMostRecentWindow(type); michael@0: } michael@0: else { michael@0: return this.curBrowser.window; michael@0: } michael@0: } michael@0: else { michael@0: return this.curFrame; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets the the window enumerator michael@0: * michael@0: * @return nsISimpleEnumerator michael@0: */ michael@0: getWinEnumerator: function MDA_getWinEnumerator() { michael@0: let type = null; michael@0: if (appName != "B2G" && this.context == "content") { michael@0: type = 'navigator:browser'; michael@0: } michael@0: return Services.wm.getEnumerator(type); michael@0: }, michael@0: michael@0: /** michael@0: * Create a new BrowserObj for window and add to known browsers michael@0: * michael@0: * @param nsIDOMWindow win michael@0: * Window for which we will create a BrowserObj michael@0: * michael@0: * @return string michael@0: * Returns the unique server-assigned ID of the window michael@0: */ michael@0: addBrowser: function MDA_addBrowser(win) { michael@0: let browser = new BrowserObj(win, this); michael@0: let winId = win.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils).outerWindowID; michael@0: winId = winId + ((appName == "B2G") ? '-b2g' : ''); michael@0: this.browsers[winId] = browser; michael@0: this.curBrowser = this.browsers[winId]; michael@0: if (this.curBrowser.elementManager.seenItems[winId] == undefined) { michael@0: //add this to seenItems so we can guarantee the user will get winId as this window's id michael@0: this.curBrowser.elementManager.seenItems[winId] = Cu.getWeakReference(win); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Start a new session in a new browser. michael@0: * michael@0: * If newSession is true, we will switch focus to the start frame michael@0: * when it registers. Also, if it is in desktop, then a new tab michael@0: * with the start page uri (about:blank) will be opened. michael@0: * michael@0: * @param nsIDOMWindow win michael@0: * Window whose browser we need to access michael@0: * @param boolean newSession michael@0: * True if this is the first time we're talking to this browser michael@0: */ michael@0: startBrowser: function MDA_startBrowser(win, newSession) { michael@0: this.mainFrame = win; michael@0: this.curFrame = null; michael@0: this.addBrowser(win); michael@0: this.curBrowser.newSession = newSession; michael@0: this.curBrowser.startSession(newSession, win, this.whenBrowserStarted.bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Callback invoked after a new session has been started in a browser. michael@0: * Loads the Marionette frame script into the browser if needed. michael@0: * michael@0: * @param nsIDOMWindow win michael@0: * Window whose browser we need to access michael@0: * @param boolean newSession michael@0: * True if this is the first time we're talking to this browser michael@0: */ michael@0: whenBrowserStarted: function MDA_whenBrowserStarted(win, newSession) { michael@0: try { michael@0: if (!Services.prefs.getBoolPref("marionette.contentListener") || !newSession) { michael@0: this.curBrowser.loadFrameScript(FRAME_SCRIPT, win); michael@0: } michael@0: } michael@0: catch (e) { michael@0: //there may not always be a content process michael@0: logger.info("could not load listener into content for page: " + win.location.href); michael@0: } michael@0: utils.window = win; michael@0: }, michael@0: michael@0: /** michael@0: * Recursively get all labeled text michael@0: * michael@0: * @param nsIDOMElement el michael@0: * The parent element michael@0: * @param array lines michael@0: * Array that holds the text lines michael@0: */ michael@0: getVisibleText: function MDA_getVisibleText(el, lines) { michael@0: let nodeName = el.nodeName; michael@0: try { michael@0: if (utils.isElementDisplayed(el)) { michael@0: if (el.value) { michael@0: lines.push(el.value); michael@0: } michael@0: for (var child in el.childNodes) { michael@0: this.getVisibleText(el.childNodes[child], lines); michael@0: }; michael@0: } michael@0: } michael@0: catch (e) { michael@0: if (nodeName == "#text") { michael@0: lines.push(el.textContent); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: getCommandId: function MDA_getCommandId() { michael@0: return this.uuidGen.generateUUID().toString(); michael@0: }, michael@0: michael@0: /** michael@0: * Given a file name, this will delete the file from the temp directory if it exists michael@0: */ michael@0: deleteFile: function(filename) { michael@0: let file = FileUtils.getFile('TmpD', [filename.toString()]); michael@0: if (file.exists()) { michael@0: file.remove(true); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Marionette API: michael@0: * michael@0: * All methods implementing a command from the client should create a michael@0: * command_id, and then use this command_id in all messages exchanged with michael@0: * the frame scripts and with responses sent to the client. This prevents michael@0: * commands and responses from getting out-of-sync, which can happen in michael@0: * the case of execute_async calls that timeout and then later send a michael@0: * response, and other situations. See bug 779011. See setScriptTimeout() michael@0: * for a basic example. michael@0: */ michael@0: michael@0: /** michael@0: * Create a new session. This creates a new BrowserObj. michael@0: * michael@0: * In a desktop environment, this opens a new browser with michael@0: * "about:blank" which subsequent commands will be sent to. michael@0: * michael@0: * This will send a hash map of supported capabilities to the client michael@0: * as part of the Marionette:register IPC command in the michael@0: * receiveMessage callback when a new browser is created. michael@0: */ michael@0: newSession: function MDA_newSession() { michael@0: this.command_id = this.getCommandId(); michael@0: this.newSessionCommandId = this.command_id; michael@0: michael@0: this.scriptTimeout = 10000; michael@0: michael@0: function waitForWindow() { michael@0: let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: let win = this.getCurrentWindow(); michael@0: if (!win || michael@0: (appName == "Firefox" && !win.gBrowser) || michael@0: (appName == "Fennec" && !win.BrowserApp)) { michael@0: checkTimer.initWithCallback(waitForWindow.bind(this), 100, michael@0: Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: else { michael@0: this.startBrowser(win, true); michael@0: } michael@0: } michael@0: michael@0: if (!Services.prefs.getBoolPref("marionette.contentListener")) { michael@0: waitForWindow.call(this); michael@0: } michael@0: else if ((appName != "Firefox") && (this.curBrowser == null)) { michael@0: // If there is a content listener, then we just wake it up michael@0: this.addBrowser(this.getCurrentWindow()); michael@0: this.curBrowser.startSession(false, this.getCurrentWindow(), michael@0: this.whenBrowserStarted); michael@0: this.messageManager.broadcastAsyncMessage("Marionette:restart", {}); michael@0: } michael@0: else { michael@0: this.sendError("Session already running", 500, null, michael@0: this.command_id); michael@0: } michael@0: this.switchToGlobalMessageManager(); michael@0: }, michael@0: michael@0: /** michael@0: * Send the current session's capabilities to the client. michael@0: * michael@0: * Capabilities informs the client of which WebDriver features are michael@0: * supported by Firefox and Marionette. They are immutable for the michael@0: * length of the session. michael@0: * michael@0: * The return value is an immutable map of string keys michael@0: * ("capabilities") to values, which may be of types boolean, michael@0: * numerical or string. michael@0: */ michael@0: getSessionCapabilities: function MDA_getSessionCapabilities() { michael@0: this.command_id = this.getCommandId(); michael@0: michael@0: let isB2G = appName == "B2G"; michael@0: let platformName = Services.appinfo.OS.toUpperCase(); michael@0: michael@0: let caps = { michael@0: // Mandated capabilities michael@0: "browserName": appName, michael@0: "browserVersion": Services.appinfo.version, michael@0: "platformName": platformName, michael@0: "platformVersion": Services.appinfo.platformVersion, michael@0: michael@0: // Supported features michael@0: "cssSelectorsEnabled": true, michael@0: "handlesAlerts": false, michael@0: "javascriptEnabled": true, michael@0: "nativeEvents": false, michael@0: "rotatable": isB2G, michael@0: "secureSsl": false, michael@0: "takesElementScreenshot": true, michael@0: "takesScreenshot": true, michael@0: michael@0: // Selenium 2 compat michael@0: "platform": platformName, michael@0: michael@0: // Proprietary extensions michael@0: "XULappId" : Services.appinfo.ID, michael@0: "appBuildId" : Services.appinfo.appBuildID, michael@0: "device": qemu == "1" ? "qemu" : (!device ? "desktop" : device), michael@0: "version": Services.appinfo.version michael@0: }; michael@0: michael@0: // eideticker (bug 965297) and mochitest (bug 965304) michael@0: // compatibility. They only check for the presence of this michael@0: // property and should so not be in caps if not on a B2G device. michael@0: if (isB2G) michael@0: caps.b2g = true; michael@0: michael@0: this.sendResponse(caps, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Log message. Accepts user defined log-level. michael@0: * michael@0: * @param object aRequest michael@0: * 'value' member holds log message michael@0: * 'level' member hold log level michael@0: */ michael@0: log: function MDA_log(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.marionetteLog.log(aRequest.parameters.value, aRequest.parameters.level); michael@0: this.sendOk(this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Return all logged messages. michael@0: */ michael@0: getLogs: function MDA_getLogs() { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendResponse(this.marionetteLog.getLogs(), this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Sets the context of the subsequent commands to be either 'chrome' or 'content' michael@0: * michael@0: * @param object aRequest michael@0: * 'value' member holds the name of the context to be switched to michael@0: */ michael@0: setContext: function MDA_setContext(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.logRequest("setContext", aRequest); michael@0: let context = aRequest.parameters.value; michael@0: if (context != "content" && context != "chrome") { michael@0: this.sendError("invalid context", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: this.context = context; michael@0: this.sendOk(this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Returns a chrome sandbox that can be used by the execute_foo functions. michael@0: * michael@0: * @param nsIDOMWindow aWindow michael@0: * Window in which we will execute code michael@0: * @param Marionette marionette michael@0: * Marionette test instance michael@0: * @param object args michael@0: * Client given args michael@0: * @return Sandbox michael@0: * Returns the sandbox michael@0: */ michael@0: createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args, specialPowers, command_id) { michael@0: try { michael@0: args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow); michael@0: } michael@0: catch(e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: return; michael@0: } michael@0: michael@0: let _chromeSandbox = new Cu.Sandbox(aWindow, michael@0: { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''}); michael@0: _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args); michael@0: _chromeSandbox.__marionetteParams = args; michael@0: _chromeSandbox.testUtils = utils; michael@0: michael@0: marionette.exports.forEach(function(fn) { michael@0: try { michael@0: _chromeSandbox[fn] = marionette[fn].bind(marionette); michael@0: } michael@0: catch(e) { michael@0: _chromeSandbox[fn] = marionette[fn]; michael@0: } michael@0: }); michael@0: michael@0: _chromeSandbox.isSystemMessageListenerReady = michael@0: function() { return systemMessageListenerReady; } michael@0: michael@0: if (specialPowers == true) { michael@0: loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js", michael@0: _chromeSandbox); michael@0: loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js", michael@0: _chromeSandbox); michael@0: loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js", michael@0: _chromeSandbox); michael@0: } michael@0: michael@0: return _chromeSandbox; michael@0: }, michael@0: michael@0: /** michael@0: * Executes a script in the given sandbox. michael@0: * michael@0: * @param Sandbox sandbox michael@0: * Sandbox in which the script will run michael@0: * @param string script michael@0: * The script to run michael@0: * @param boolean directInject michael@0: * If true, then the script will be run as is, michael@0: * and not as a function body (as you would michael@0: * do using the WebDriver spec) michael@0: * @param boolean async michael@0: * True if the script is asynchronous michael@0: */ michael@0: executeScriptInSandbox: function MDA_executeScriptInSandbox(sandbox, script, michael@0: directInject, async, command_id, timeout) { michael@0: michael@0: if (directInject && async && michael@0: (timeout == null || timeout == 0)) { michael@0: this.sendError("Please set a timeout", 21, null, command_id); michael@0: return; michael@0: } michael@0: michael@0: if (this.importedScripts.exists()) { michael@0: let stream = Cc["@mozilla.org/network/file-input-stream;1"]. michael@0: createInstance(Ci.nsIFileInputStream); michael@0: stream.init(this.importedScripts, -1, 0, 0); michael@0: let data = NetUtil.readInputStreamToString(stream, stream.available()); michael@0: script = data + script; michael@0: } michael@0: michael@0: let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0); michael@0: michael@0: if (directInject && !async && michael@0: (res == undefined || res.passed == undefined)) { michael@0: this.sendError("finish() not called", 500, null, command_id); michael@0: return; michael@0: } michael@0: michael@0: if (!async) { michael@0: this.sendResponse(this.curBrowser.elementManager.wrapValue(res), michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Execute the given script either as a function body (executeScript) michael@0: * or directly (for 'mochitest' like JS Marionette tests) michael@0: * michael@0: * @param object aRequest michael@0: * 'script' member is the script to run michael@0: * 'args' member holds the arguments to the script michael@0: * @param boolean directInject michael@0: * if true, it will be run directly and not as a michael@0: * function body michael@0: */ michael@0: execute: function MDA_execute(aRequest, directInject) { michael@0: let inactivityTimeout = aRequest.parameters.inactivityTimeout; michael@0: let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout; michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: let script; michael@0: this.logRequest("execute", aRequest); michael@0: if (aRequest.parameters.newSandbox == undefined) { michael@0: //if client does not send a value in newSandbox, michael@0: //then they expect the same behaviour as webdriver michael@0: aRequest.parameters.newSandbox = true; michael@0: } michael@0: if (this.context == "content") { michael@0: this.sendAsync("executeScript", michael@0: { michael@0: script: aRequest.parameters.script, michael@0: args: aRequest.parameters.args, michael@0: newSandbox: aRequest.parameters.newSandbox, michael@0: timeout: timeout, michael@0: specialPowers: aRequest.parameters.specialPowers, michael@0: filename: aRequest.parameters.filename, michael@0: line: aRequest.parameters.line michael@0: }, michael@0: command_id); michael@0: return; michael@0: } michael@0: michael@0: // handle the inactivity timeout michael@0: let that = this; michael@0: if (inactivityTimeout) { michael@0: let inactivityTimeoutHandler = function(message, status) { michael@0: let error_msg = {message: value, status: status}; michael@0: that.sendToClient({from: that.actorID, error: error_msg}, michael@0: marionette.command_id); michael@0: }; michael@0: let setTimer = function() { michael@0: that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: if (that.inactivityTimer != null) { michael@0: that.inactivityTimer.initWithCallback(function() { michael@0: inactivityTimeoutHandler("timed out due to inactivity", 28); michael@0: }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT); michael@0: } michael@0: } michael@0: setTimer(); michael@0: this.heartbeatCallback = function resetInactivityTimer() { michael@0: that.inactivityTimer.cancel(); michael@0: setTimer(); michael@0: } michael@0: } michael@0: michael@0: let curWindow = this.getCurrentWindow(); michael@0: let marionette = new Marionette(this, curWindow, "chrome", michael@0: this.marionetteLog, michael@0: timeout, this.heartbeatCallback, this.testName); michael@0: let _chromeSandbox = this.createExecuteSandbox(curWindow, michael@0: marionette, michael@0: aRequest.parameters.args, michael@0: aRequest.parameters.specialPowers, michael@0: command_id); michael@0: if (!_chromeSandbox) michael@0: return; michael@0: michael@0: try { michael@0: _chromeSandbox.finish = function chromeSandbox_finish() { michael@0: if (that.inactivityTimer != null) { michael@0: that.inactivityTimer.cancel(); michael@0: } michael@0: return marionette.generate_results(); michael@0: }; michael@0: michael@0: if (directInject) { michael@0: script = aRequest.parameters.script; michael@0: } michael@0: else { michael@0: script = "let func = function() {" + michael@0: aRequest.parameters.script + michael@0: "};" + michael@0: "func.apply(null, __marionetteParams);"; michael@0: } michael@0: this.executeScriptInSandbox(_chromeSandbox, script, directInject, michael@0: false, command_id, timeout); michael@0: } michael@0: catch (e) { michael@0: let error = createStackMessage(e, michael@0: "execute_script", michael@0: aRequest.parameters.filename, michael@0: aRequest.parameters.line, michael@0: script); michael@0: this.sendError(error[0], 17, error[1], command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Set the timeout for asynchronous script execution michael@0: * michael@0: * @param object aRequest michael@0: * 'ms' member is time in milliseconds to set timeout michael@0: */ michael@0: setScriptTimeout: function MDA_setScriptTimeout(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: let timeout = parseInt(aRequest.parameters.ms); michael@0: if(isNaN(timeout)){ michael@0: this.sendError("Not a Number", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: this.scriptTimeout = timeout; michael@0: this.sendOk(this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * execute pure JS script. Used to execute 'mochitest'-style Marionette tests. michael@0: * michael@0: * @param object aRequest michael@0: * 'script' member holds the script to execute michael@0: * 'args' member holds the arguments to the script michael@0: * 'timeout' member will be used as the script timeout if it is given michael@0: */ michael@0: executeJSScript: function MDA_executeJSScript(aRequest) { michael@0: let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout; michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: michael@0: //all pure JS scripts will need to call Marionette.finish() to complete the test. michael@0: if (aRequest.newSandbox == undefined) { michael@0: //if client does not send a value in newSandbox, michael@0: //then they expect the same behaviour as webdriver michael@0: aRequest.newSandbox = true; michael@0: } michael@0: if (this.context == "chrome") { michael@0: if (aRequest.parameters.async) { michael@0: this.executeWithCallback(aRequest, aRequest.parameters.async); michael@0: } michael@0: else { michael@0: this.execute(aRequest, true); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("executeJSScript", michael@0: { michael@0: script: aRequest.parameters.script, michael@0: args: aRequest.parameters.args, michael@0: newSandbox: aRequest.parameters.newSandbox, michael@0: async: aRequest.parameters.async, michael@0: timeout: timeout, michael@0: inactivityTimeout: aRequest.parameters.inactivityTimeout, michael@0: specialPowers: aRequest.parameters.specialPowers, michael@0: filename: aRequest.parameters.filename, michael@0: line: aRequest.parameters.line, michael@0: }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * This function is used by executeAsync and executeJSScript to execute a script michael@0: * in a sandbox. michael@0: * michael@0: * For executeJSScript, it will return a message only when the finish() method is called. michael@0: * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] michael@0: * method is called, or if it times out. michael@0: * michael@0: * @param object aRequest michael@0: * 'script' member holds the script to execute michael@0: * 'args' member holds the arguments for the script michael@0: * @param boolean directInject michael@0: * if true, it will be run directly and not as a michael@0: * function body michael@0: */ michael@0: executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) { michael@0: let inactivityTimeout = aRequest.parameters.inactivityTimeout; michael@0: let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout; michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: let script; michael@0: this.logRequest("executeWithCallback", aRequest); michael@0: if (aRequest.parameters.newSandbox == undefined) { michael@0: //if client does not send a value in newSandbox, michael@0: //then they expect the same behaviour as webdriver michael@0: aRequest.parameters.newSandbox = true; michael@0: } michael@0: michael@0: if (this.context == "content") { michael@0: this.sendAsync("executeAsyncScript", michael@0: { michael@0: script: aRequest.parameters.script, michael@0: args: aRequest.parameters.args, michael@0: id: this.command_id, michael@0: newSandbox: aRequest.parameters.newSandbox, michael@0: timeout: timeout, michael@0: inactivityTimeout: inactivityTimeout, michael@0: specialPowers: aRequest.parameters.specialPowers, michael@0: filename: aRequest.parameters.filename, michael@0: line: aRequest.parameters.line michael@0: }, michael@0: command_id); michael@0: return; michael@0: } michael@0: michael@0: // handle the inactivity timeout michael@0: let that = this; michael@0: if (inactivityTimeout) { michael@0: this.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: if (this.inactivityTimer != null) { michael@0: this.inactivityTimer.initWithCallback(function() { michael@0: chromeAsyncReturnFunc("timed out due to inactivity", 28); michael@0: }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT); michael@0: } michael@0: this.heartbeatCallback = function resetInactivityTimer() { michael@0: that.inactivityTimer.cancel(); michael@0: that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: if (that.inactivityTimer != null) { michael@0: that.inactivityTimer.initWithCallback(function() { michael@0: chromeAsyncReturnFunc("timed out due to inactivity", 28); michael@0: }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT); michael@0: } michael@0: } michael@0: } michael@0: michael@0: let curWindow = this.getCurrentWindow(); michael@0: let original_onerror = curWindow.onerror; michael@0: let that = this; michael@0: that.timeout = timeout; michael@0: let marionette = new Marionette(this, curWindow, "chrome", michael@0: this.marionetteLog, michael@0: timeout, this.heartbeatCallback, this.testName); michael@0: marionette.command_id = this.command_id; michael@0: michael@0: function chromeAsyncReturnFunc(value, status, stacktrace) { michael@0: if (that._emu_cbs && Object.keys(that._emu_cbs).length) { michael@0: value = "Emulator callback still pending when finish() called"; michael@0: status = 500; michael@0: that._emu_cbs = null; michael@0: } michael@0: michael@0: if (value == undefined) michael@0: value = null; michael@0: if (that.command_id == marionette.command_id) { michael@0: if (that.timer != null) { michael@0: that.timer.cancel(); michael@0: that.timer = null; michael@0: } michael@0: michael@0: curWindow.onerror = original_onerror; michael@0: michael@0: if (status == 0 || status == undefined) { michael@0: that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status}, michael@0: marionette.command_id); michael@0: } michael@0: else { michael@0: let error_msg = {message: value, status: status, stacktrace: stacktrace}; michael@0: that.sendToClient({from: that.actorID, error: error_msg}, michael@0: marionette.command_id); michael@0: } michael@0: } michael@0: if (that.inactivityTimer != null) { michael@0: that.inactivityTimer.cancel(); michael@0: } michael@0: } michael@0: michael@0: curWindow.onerror = function (errorMsg, url, lineNumber) { michael@0: chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17); michael@0: return true; michael@0: }; michael@0: michael@0: function chromeAsyncFinish() { michael@0: chromeAsyncReturnFunc(marionette.generate_results(), 0); michael@0: } michael@0: michael@0: let _chromeSandbox = this.createExecuteSandbox(curWindow, michael@0: marionette, michael@0: aRequest.parameters.args, michael@0: aRequest.parameters.specialPowers, michael@0: command_id); michael@0: if (!_chromeSandbox) michael@0: return; michael@0: michael@0: try { michael@0: michael@0: this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: if (this.timer != null) { michael@0: this.timer.initWithCallback(function() { michael@0: chromeAsyncReturnFunc("timed out", 28); michael@0: }, that.timeout, Ci.nsITimer.TYPE_ONESHOT); michael@0: } michael@0: michael@0: _chromeSandbox.returnFunc = chromeAsyncReturnFunc; michael@0: _chromeSandbox.finish = chromeAsyncFinish; michael@0: michael@0: if (directInject) { michael@0: script = aRequest.parameters.script; michael@0: } michael@0: else { michael@0: script = '__marionetteParams.push(returnFunc);' michael@0: + 'let marionetteScriptFinished = returnFunc;' michael@0: + 'let __marionetteFunc = function() {' + aRequest.parameters.script + '};' michael@0: + '__marionetteFunc.apply(null, __marionetteParams);'; michael@0: } michael@0: michael@0: this.executeScriptInSandbox(_chromeSandbox, script, directInject, michael@0: true, command_id, timeout); michael@0: } catch (e) { michael@0: let error = createStackMessage(e, michael@0: "execute_async_script", michael@0: aRequest.parameters.filename, michael@0: aRequest.parameters.line, michael@0: script); michael@0: chromeAsyncReturnFunc(error[0], 17, error[1]); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Navigate to to given URL. michael@0: * michael@0: * This will follow redirects issued by the server. When the method michael@0: * returns is based on the page load strategy that the user has michael@0: * selected. michael@0: * michael@0: * Documents that contain a META tag with the "http-equiv" attribute michael@0: * set to "refresh" will return if the timeout is greater than 1 michael@0: * second and the other criteria for determining whether a page is michael@0: * loaded are met. When the refresh period is 1 second or less and michael@0: * the page load strategy is "normal" or "conservative", it will michael@0: * wait for the page to complete loading before returning. michael@0: * michael@0: * If any modal dialog box, such as those opened on michael@0: * window.onbeforeunload or window.alert, is opened at any point in michael@0: * the page load, it will return immediately. michael@0: * michael@0: * If a 401 response is seen by the browser, it will return michael@0: * immediately. That is, if BASIC, DIGEST, NTLM or similar michael@0: * authentication is required, the page load is assumed to be michael@0: * complete. This does not include FORM-based authentication. michael@0: * michael@0: * @param object aRequest where url property holds the michael@0: * URL to navigate to michael@0: */ michael@0: get: function MDA_get(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context != "chrome") { michael@0: aRequest.command_id = command_id; michael@0: aRequest.parameters.pageTimeout = this.pageTimeout; michael@0: this.sendAsync("get", aRequest.parameters, command_id); michael@0: return; michael@0: } michael@0: michael@0: this.getCurrentWindow().location.href = aRequest.parameters.url; michael@0: let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: let start = new Date().getTime(); michael@0: let end = null; michael@0: michael@0: function checkLoad() { michael@0: end = new Date().getTime(); michael@0: let elapse = end - start; michael@0: if (this.pageTimeout == null || elapse <= this.pageTimeout){ michael@0: if (curWindow.document.readyState == "complete") { michael@0: sendOk(command_id); michael@0: return; michael@0: } michael@0: else{ michael@0: checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: else{ michael@0: sendError("Error loading page", 13, null, command_id); michael@0: return; michael@0: } michael@0: } michael@0: checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: /** michael@0: * Get a string representing the current URL. michael@0: * michael@0: * On Desktop this returns a string representation of the URL of the michael@0: * current top level browsing context. This is equivalent to michael@0: * document.location.href. michael@0: * michael@0: * When in the context of the chrome, this returns the canonical URL michael@0: * of the current resource. michael@0: */ michael@0: getCurrentUrl: function MDA_getCurrentUrl() { michael@0: this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: this.sendResponse(this.getCurrentWindow().location.href, this.command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("getCurrentUrl", {}, this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets the current title of the window michael@0: */ michael@0: getTitle: function MDA_getTitle() { michael@0: this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome"){ michael@0: var curWindow = this.getCurrentWindow(); michael@0: var title = curWindow.document.documentElement.getAttribute('title'); michael@0: this.sendResponse(title, this.command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("getTitle", {}, this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Gets the current type of the window michael@0: */ michael@0: getWindowType: function MDA_getWindowType() { michael@0: this.command_id = this.getCommandId(); michael@0: var curWindow = this.getCurrentWindow(); michael@0: var type = curWindow.document.documentElement.getAttribute('windowtype'); michael@0: this.sendResponse(type, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the page source of the content document michael@0: */ michael@0: getPageSource: function MDA_getPageSource(){ michael@0: this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome"){ michael@0: let curWindow = this.getCurrentWindow(); michael@0: let XMLSerializer = curWindow.XMLSerializer; michael@0: let pageSource = new XMLSerializer().serializeToString(curWindow.document); michael@0: this.sendResponse(pageSource, this.command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("getPageSource", {}, this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Go back in history michael@0: */ michael@0: goBack: function MDA_goBack() { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("goBack", {}, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Go forward in history michael@0: */ michael@0: goForward: function MDA_goForward() { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("goForward", {}, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Refresh the page michael@0: */ michael@0: refresh: function MDA_refresh() { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("refresh", {}, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Get the current window's handle. michael@0: * michael@0: * Return an opaque server-assigned identifier to this window that michael@0: * uniquely identifies it within this Marionette instance. This can michael@0: * be used to switch to this window at a later point. michael@0: * michael@0: * @return unique window handle (string) michael@0: */ michael@0: getWindowHandle: function MDA_getWindowHandle() { michael@0: this.command_id = this.getCommandId(); michael@0: for (let i in this.browsers) { michael@0: if (this.curBrowser == this.browsers[i]) { michael@0: this.sendResponse(i, this.command_id); michael@0: return; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Get list of windows in the current context. michael@0: * michael@0: * If called in the content context it will return a list of michael@0: * references to all available browser windows. Called in the michael@0: * chrome context, it will list all available windows, not just michael@0: * browser windows (e.g. not just navigator.browser). michael@0: * michael@0: * Each window handle is assigned by the server, and the array of michael@0: * strings returned does not have a guaranteed ordering. michael@0: * michael@0: * @return unordered array of unique window handles as strings michael@0: */ michael@0: getWindowHandles: function MDA_getWindowHandles() { michael@0: this.command_id = this.getCommandId(); michael@0: let res = []; michael@0: let winEn = this.getWinEnumerator(); michael@0: while (winEn.hasMoreElements()) { michael@0: let foundWin = winEn.getNext(); michael@0: let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils).outerWindowID; michael@0: winId = winId + ((appName == "B2G") ? "-b2g" : ""); michael@0: res.push(winId); michael@0: } michael@0: this.sendResponse(res, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Switch to a window based on name or server-assigned id. michael@0: * Searches based on name, then id. michael@0: * michael@0: * @param object aRequest michael@0: * 'name' member holds the name or id of the window to switch to michael@0: */ michael@0: switchToWindow: function MDA_switchToWindow(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: let winEn = this.getWinEnumerator(); michael@0: while(winEn.hasMoreElements()) { michael@0: let foundWin = winEn.getNext(); michael@0: let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils) michael@0: .outerWindowID; michael@0: winId = winId + ((appName == "B2G") ? '-b2g' : ''); michael@0: if (aRequest.parameters.name == foundWin.name || aRequest.parameters.name == winId) { michael@0: if (this.browsers[winId] == undefined) { michael@0: //enable Marionette in that browser window michael@0: this.startBrowser(foundWin, false); michael@0: } michael@0: else { michael@0: utils.window = foundWin; michael@0: this.curBrowser = this.browsers[winId]; michael@0: } michael@0: this.sendOk(command_id); michael@0: return; michael@0: } michael@0: } michael@0: this.sendError("Unable to locate window " + aRequest.parameters.name, 23, null, michael@0: command_id); michael@0: }, michael@0: michael@0: getActiveFrame: function MDA_getActiveFrame() { michael@0: this.command_id = this.getCommandId(); michael@0: michael@0: if (this.context == "chrome") { michael@0: if (this.curFrame) { michael@0: let frameUid = this.curBrowser.elementManager.addToKnownElements(this.curFrame.frameElement); michael@0: this.sendResponse(frameUid, this.command_id); michael@0: } else { michael@0: // no current frame, we're at toplevel michael@0: this.sendResponse(null, this.command_id); michael@0: } michael@0: } else { michael@0: // not chrome michael@0: this.sendResponse(this.currentFrameElement, this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Switch to a given frame within the current window michael@0: * michael@0: * @param object aRequest michael@0: * 'element' is the element to switch to michael@0: * 'id' if element is not set, then this michael@0: * holds either the id, name or index michael@0: * of the frame to switch to michael@0: */ michael@0: switchToFrame: function MDA_switchToFrame(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: this.logRequest("switchToFrame", aRequest); michael@0: let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: let curWindow = this.getCurrentWindow(); michael@0: let checkLoad = function() { michael@0: let errorRegex = /about:.+(error)|(blocked)\?/; michael@0: let curWindow = this.getCurrentWindow(); michael@0: if (curWindow.document.readyState == "complete") { michael@0: this.sendOk(command_id); michael@0: return; michael@0: } michael@0: else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)) { michael@0: this.sendError("Error loading page", 13, null, command_id); michael@0: return; michael@0: } michael@0: michael@0: checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: if (this.context == "chrome") { michael@0: let foundFrame = null; michael@0: if ((aRequest.parameters.id == null) && (aRequest.parameters.element == null)) { michael@0: this.curFrame = null; michael@0: if (aRequest.parameters.focus) { michael@0: this.mainFrame.focus(); michael@0: } michael@0: checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return; michael@0: } michael@0: if (aRequest.parameters.element != undefined) { michael@0: if (this.curBrowser.elementManager.seenItems[aRequest.parameters.element]) { michael@0: let wantedFrame = this.curBrowser.elementManager.getKnownElement(aRequest.parameters.element, curWindow); //HTMLIFrameElement michael@0: let frames = curWindow.document.getElementsByTagName("iframe"); michael@0: let numFrames = frames.length; michael@0: for (let i = 0; i < numFrames; i++) { michael@0: if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) { michael@0: curWindow = frames[i].contentWindow; michael@0: this.curFrame = curWindow; michael@0: if (aRequest.parameters.focus) { michael@0: this.curFrame.focus(); michael@0: } michael@0: checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: switch(typeof(aRequest.parameters.id)) { michael@0: case "string" : michael@0: let foundById = null; michael@0: let frames = curWindow.document.getElementsByTagName("iframe"); michael@0: let numFrames = frames.length; michael@0: for (let i = 0; i < numFrames; i++) { michael@0: //give precedence to name michael@0: let frame = frames[i]; michael@0: if (frame.getAttribute("name") == aRequest.parameters.id) { michael@0: foundFrame = i; michael@0: curWindow = frame.contentWindow; michael@0: break; michael@0: } else if ((foundById == null) && (frame.id == aRequest.parameters.id)) { michael@0: foundById = i; michael@0: } michael@0: } michael@0: if ((foundFrame == null) && (foundById != null)) { michael@0: foundFrame = foundById; michael@0: curWindow = frames[foundById].contentWindow; michael@0: } michael@0: break; michael@0: case "number": michael@0: if (curWindow.frames[aRequest.parameters.id] != undefined) { michael@0: foundFrame = aRequest.parameters.id; michael@0: curWindow = curWindow.frames[foundFrame].frameElement.contentWindow; michael@0: } michael@0: break; michael@0: } michael@0: if (foundFrame != null) { michael@0: this.curFrame = curWindow; michael@0: if (aRequest.parameters.focus) { michael@0: this.curFrame.focus(); michael@0: } michael@0: checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } else { michael@0: this.sendError("Unable to locate frame: " + aRequest.parameters.id, 8, null, michael@0: command_id); michael@0: } michael@0: } michael@0: else { michael@0: if ((!aRequest.parameters.id) && (!aRequest.parameters.element) && michael@0: (this.curBrowser.frameManager.currentRemoteFrame !== null)) { michael@0: // We're currently using a ChromeMessageSender for a remote frame, so this michael@0: // request indicates we need to switch back to the top-level (parent) frame. michael@0: // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so michael@0: // we send the message to the right listener. michael@0: this.switchToGlobalMessageManager(); michael@0: } michael@0: aRequest.command_id = command_id; michael@0: this.sendAsync("switchToFrame", aRequest.parameters, command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Set timeout for searching for elements michael@0: * michael@0: * @param object aRequest michael@0: * 'ms' holds the search timeout in milliseconds michael@0: */ michael@0: setSearchTimeout: function MDA_setSearchTimeout(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: let timeout = parseInt(aRequest.parameters.ms); michael@0: if (isNaN(timeout)) { michael@0: this.sendError("Not a Number", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: this.searchTimeout = timeout; michael@0: this.sendOk(this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Set timeout for page loading, searching and scripts michael@0: * michael@0: * @param object aRequest michael@0: * 'type' hold the type of timeout michael@0: * 'ms' holds the timeout in milliseconds michael@0: */ michael@0: timeouts: function MDA_timeouts(aRequest){ michael@0: /*setTimeout*/ michael@0: this.command_id = this.getCommandId(); michael@0: let timeout_type = aRequest.parameters.type; michael@0: let timeout = parseInt(aRequest.parameters.ms); michael@0: if (isNaN(timeout)) { michael@0: this.sendError("Not a Number", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: if (timeout_type == "implicit") { michael@0: this.setSearchTimeout(aRequest); michael@0: } michael@0: else if (timeout_type == "script") { michael@0: this.setScriptTimeout(aRequest); michael@0: } michael@0: else { michael@0: this.pageTimeout = timeout; michael@0: this.sendOk(this.command_id); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Single Tap michael@0: * michael@0: * @param object aRequest michael@0: 'element' represents the ID of the element to single tap on michael@0: */ michael@0: singleTap: function MDA_singleTap(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: let serId = aRequest.parameters.id; michael@0: let x = aRequest.parameters.x; michael@0: let y = aRequest.parameters.y; michael@0: if (this.context == "chrome") { michael@0: this.sendError("Command 'singleTap' is not available in chrome context", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("singleTap", michael@0: { michael@0: id: serId, michael@0: corx: x, michael@0: cory: y michael@0: }, michael@0: this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * actionChain michael@0: * michael@0: * @param object aRequest michael@0: * 'value' represents a nested array: inner array represents each event; outer array represents collection of events michael@0: */ michael@0: actionChain: function MDA_actionChain(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: this.sendError("Command 'actionChain' is not available in chrome context", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("actionChain", michael@0: { michael@0: chain: aRequest.parameters.chain, michael@0: nextId: aRequest.parameters.nextId michael@0: }, michael@0: this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * multiAction michael@0: * michael@0: * @param object aRequest michael@0: * 'value' represents a nested array: inner array represents each event; michael@0: * middle array represents collection of events for each finger michael@0: * outer array represents all the fingers michael@0: */ michael@0: michael@0: multiAction: function MDA_multiAction(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: this.sendError("Command 'multiAction' is not available in chrome context", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("multiAction", michael@0: { michael@0: value: aRequest.parameters.value, michael@0: maxlen: aRequest.parameters.max_length michael@0: }, michael@0: this.command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Find an element using the indicated search strategy. michael@0: * michael@0: * @param object aRequest michael@0: * 'using' member indicates which search method to use michael@0: * 'value' member is the value the client is looking for michael@0: */ michael@0: findElement: function MDA_findElement(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: let id; michael@0: try { michael@0: let on_success = this.sendResponse.bind(this); michael@0: let on_error = this.sendError.bind(this); michael@0: id = this.curBrowser.elementManager.find( michael@0: this.getCurrentWindow(), michael@0: aRequest.parameters, michael@0: this.searchTimeout, michael@0: on_success, michael@0: on_error, michael@0: false, michael@0: command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("findElementContent", michael@0: { michael@0: value: aRequest.parameters.value, michael@0: using: aRequest.parameters.using, michael@0: element: aRequest.parameters.element, michael@0: searchTimeout: this.searchTimeout michael@0: }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Find elements using the indicated search strategy. michael@0: * michael@0: * @param object aRequest michael@0: * 'using' member indicates which search method to use michael@0: * 'value' member is the value the client is looking for michael@0: */ michael@0: findElements: function MDA_findElements(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: let id; michael@0: try { michael@0: let on_success = this.sendResponse.bind(this); michael@0: let on_error = this.sendError.bind(this); michael@0: id = this.curBrowser.elementManager.find(this.getCurrentWindow(), michael@0: aRequest.parameters, michael@0: this.searchTimeout, michael@0: on_success, michael@0: on_error, michael@0: true, michael@0: command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("findElementsContent", michael@0: { michael@0: value: aRequest.parameters.value, michael@0: using: aRequest.parameters.using, michael@0: element: aRequest.parameters.element, michael@0: searchTimeout: this.searchTimeout michael@0: }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Return the active element on the page michael@0: */ michael@0: getActiveElement: function MDA_getActiveElement(){ michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: this.sendAsync("getActiveElement", {}, command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Send click event to element michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be clicked michael@0: */ michael@0: clickElement: function MDA_clickElementent(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: //NOTE: click atom fails, fall back to click() action michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: el.click(); michael@0: this.sendOk(command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: // We need to protect against the click causing an OOP frame to close. michael@0: // This fires the mozbrowserclose event when it closes so we need to michael@0: // listen for it and then just send an error back. The person making the michael@0: // call should be aware something isnt right and handle accordingly michael@0: let curWindow = this.getCurrentWindow(); michael@0: let self = this; michael@0: this.mozBrowserClose = function() { michael@0: curWindow.removeEventListener('mozbrowserclose', self.mozBrowserClose, true); michael@0: self.switchToGlobalMessageManager(); michael@0: self.sendError("The frame closed during the click, recovering to allow further communications", 500, null, command_id); michael@0: }; michael@0: curWindow.addEventListener('mozbrowserclose', this.mozBrowserClose, true); michael@0: this.sendAsync("clickElement", michael@0: { id: aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Get a given attribute of an element michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be inspected michael@0: * 'name' member holds the name of the attribute to retrieve michael@0: */ michael@0: getElementAttribute: function MDA_getElementAttribute(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: this.sendResponse(utils.getElementAttribute(el, aRequest.parameters.name), michael@0: command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("getElementAttribute", michael@0: { michael@0: id: aRequest.parameters.id, michael@0: name: aRequest.parameters.name michael@0: }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Get the text of an element, if any. Includes the text of all child elements. michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be inspected michael@0: */ michael@0: getElementText: function MDA_getElementText(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: //Note: for chrome, we look at text nodes, and any node with a "label" field michael@0: try { michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: let lines = []; michael@0: this.getVisibleText(el, lines); michael@0: lines = lines.join("\n"); michael@0: this.sendResponse(lines, command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("getElementText", michael@0: { id: aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Get the tag name of the element. michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be inspected michael@0: */ michael@0: getElementTagName: function MDA_getElementTagName(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: this.sendResponse(el.tagName.toLowerCase(), command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("getElementTagName", michael@0: { id: aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Check if element is displayed michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be checked michael@0: */ michael@0: isElementDisplayed: function MDA_isElementDisplayed(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: this.sendResponse(utils.isElementDisplayed(el), command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("isElementDisplayed", michael@0: { id:aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Return the property of the computed style of an element michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be checked michael@0: * 'propertyName' is the CSS rule that is being requested michael@0: */ michael@0: getElementValueOfCssProperty: function MDA_getElementValueOfCssProperty(aRequest){ michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: this.sendAsync("getElementValueOfCssProperty", michael@0: {id: aRequest.parameters.id, propertyName: aRequest.parameters.propertyName}, michael@0: command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Submit a form on a content page by either using form or element in a form michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be checked michael@0: */ michael@0: submitElement: function MDA_submitElement(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: this.sendError("Command 'submitElement' is not available in chrome context", 500, null, this.command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("submitElement", {id: aRequest.parameters.id}, command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Check if element is enabled michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be checked michael@0: */ michael@0: isElementEnabled: function MDA_isElementEnabled(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: //Selenium atom doesn't quite work here michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: if (el.disabled != undefined) { michael@0: this.sendResponse(!!!el.disabled, command_id); michael@0: } michael@0: else { michael@0: this.sendResponse(true, command_id); michael@0: } michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("isElementEnabled", michael@0: { id:aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Check if element is selected michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be checked michael@0: */ michael@0: isElementSelected: function MDA_isElementSelected(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: //Selenium atom doesn't quite work here michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: if (el.checked != undefined) { michael@0: this.sendResponse(!!el.checked, command_id); michael@0: } michael@0: else if (el.selected != undefined) { michael@0: this.sendResponse(!!el.selected, command_id); michael@0: } michael@0: else { michael@0: this.sendResponse(true, command_id); michael@0: } michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("isElementSelected", michael@0: { id:aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: getElementSize: function MDA_getElementSize(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: let clientRect = el.getBoundingClientRect(); michael@0: this.sendResponse({width: clientRect.width, height: clientRect.height}, michael@0: command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("getElementSize", michael@0: { id:aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Send key presses to element after focusing on it michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be checked michael@0: * 'value' member holds the value to send to the element michael@0: */ michael@0: sendKeysToElement: function MDA_sendKeysToElement(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: try { michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: el.focus(); michael@0: utils.sendString(aRequest.parameters.value.join(""), utils.window); michael@0: this.sendOk(command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("sendKeysToElement", michael@0: { michael@0: id:aRequest.parameters.id, michael@0: value: aRequest.parameters.value michael@0: }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Sets the test name michael@0: * michael@0: * The test name is used in logging messages. michael@0: */ michael@0: setTestName: function MDA_setTestName(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.logRequest("setTestName", aRequest); michael@0: this.testName = aRequest.parameters.value; michael@0: this.sendAsync("setTestName", michael@0: { value: aRequest.parameters.value }, michael@0: this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Clear the text of an element michael@0: * michael@0: * @param object aRequest michael@0: * 'id' member holds the reference id to michael@0: * the element that will be cleared michael@0: */ michael@0: clearElement: function MDA_clearElement(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (this.context == "chrome") { michael@0: //the selenium atom doesn't work here michael@0: try { michael@0: let el = this.curBrowser.elementManager.getKnownElement( michael@0: aRequest.parameters.id, this.getCurrentWindow()); michael@0: if (el.nodeName == "textbox") { michael@0: el.value = ""; michael@0: } michael@0: else if (el.nodeName == "checkbox") { michael@0: el.checked = false; michael@0: } michael@0: this.sendOk(command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: else { michael@0: this.sendAsync("clearElement", michael@0: { id:aRequest.parameters.id }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Get an element's location on the page. michael@0: * michael@0: * The returned point will contain the x and y coordinates of the michael@0: * top left-hand corner of the given element. The point (0,0) michael@0: * refers to the upper-left corner of the document. michael@0: * michael@0: * @return a point containing x and y coordinates as properties michael@0: */ michael@0: getElementLocation: function MDA_getElementLocation(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("getElementLocation", {id: aRequest.parameters.id}, michael@0: this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Add a cookie to the document. michael@0: */ michael@0: addCookie: function MDA_addCookie(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("addCookie", michael@0: { cookie:aRequest.parameters.cookie }, michael@0: this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Get all the cookies for the current domain. michael@0: * michael@0: * This is the equivalent of calling "document.cookie" and parsing michael@0: * the result. michael@0: */ michael@0: getCookies: function MDA_getCookies() { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("getCookies", {}, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Delete all cookies that are visible to a document michael@0: */ michael@0: deleteAllCookies: function MDA_deleteAllCookies() { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("deleteAllCookies", {}, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Delete a cookie by name michael@0: */ michael@0: deleteCookie: function MDA_deleteCookie(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("deleteCookie", michael@0: { name:aRequest.parameters.name }, michael@0: this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Close the current window, ending the session if it's the last michael@0: * window currently open. michael@0: * michael@0: * On B2G this method is a noop and will return immediately. michael@0: */ michael@0: close: function MDA_close() { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: if (appName == "B2G") { michael@0: // We can't close windows so just return michael@0: this.sendOk(command_id); michael@0: } michael@0: else { michael@0: // Get the total number of windows michael@0: let numOpenWindows = 0; michael@0: let winEnum = this.getWinEnumerator(); michael@0: while (winEnum.hasMoreElements()) { michael@0: numOpenWindows += 1; michael@0: winEnum.getNext(); michael@0: } michael@0: michael@0: // if there is only 1 window left, delete the session michael@0: if (numOpenWindows === 1) { michael@0: try { michael@0: this.sessionTearDown(); michael@0: } michael@0: catch (e) { michael@0: this.sendError("Could not clear session", 500, michael@0: e.name + ": " + e.message, command_id); michael@0: return; michael@0: } michael@0: this.sendOk(command_id); michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: this.messageManager.removeDelayedFrameScript(FRAME_SCRIPT); michael@0: this.getCurrentWindow().close(); michael@0: this.sendOk(command_id); michael@0: } michael@0: catch (e) { michael@0: this.sendError("Could not close window: " + e.message, 13, e.stack, michael@0: command_id); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Deletes the session. michael@0: * michael@0: * If it is a desktop environment, it will close the session's tab and close all listeners michael@0: * michael@0: * If it is a B2G environment, it will make the main content listener sleep, and close michael@0: * all other listeners. The main content listener persists after disconnect (it's the homescreen), michael@0: * and can safely be reused. michael@0: */ michael@0: sessionTearDown: function MDA_sessionTearDown() { michael@0: if (this.curBrowser != null) { michael@0: if (appName == "B2G") { michael@0: this.globalMessageManager.broadcastAsyncMessage( michael@0: "Marionette:sleepSession" + this.curBrowser.mainContentId, {}); michael@0: this.curBrowser.knownFrames.splice( michael@0: this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1); michael@0: } michael@0: else { michael@0: //don't set this pref for B2G since the framescript can be safely reused michael@0: Services.prefs.setBoolPref("marionette.contentListener", false); michael@0: } michael@0: this.curBrowser.closeTab(); michael@0: //delete session in each frame in each browser michael@0: for (let win in this.browsers) { michael@0: for (let i in this.browsers[win].knownFrames) { michael@0: this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {}); michael@0: } michael@0: } michael@0: let winEnum = this.getWinEnumerator(); michael@0: while (winEnum.hasMoreElements()) { michael@0: winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT); michael@0: } michael@0: this.curBrowser.frameManager.removeMessageManagerListeners(this.globalMessageManager); michael@0: } michael@0: this.switchToGlobalMessageManager(); michael@0: // reset frame to the top-most frame michael@0: this.curFrame = null; michael@0: if (this.mainFrame) { michael@0: this.mainFrame.focus(); michael@0: } michael@0: this.deleteFile('marionetteChromeScripts'); michael@0: this.deleteFile('marionetteContentScripts'); michael@0: }, michael@0: michael@0: /** michael@0: * Processes the 'deleteSession' request from the client by tearing down michael@0: * the session and responding 'ok'. michael@0: */ michael@0: deleteSession: function MDA_deleteSession() { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: try { michael@0: this.sessionTearDown(); michael@0: } michael@0: catch (e) { michael@0: this.sendError("Could not delete session", 500, e.name + ": " + e.message, command_id); michael@0: return; michael@0: } michael@0: this.sendOk(command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the current status of the Application Cache michael@0: */ michael@0: getAppCacheStatus: function MDA_getAppCacheStatus(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("getAppCacheStatus", {}, this.command_id); michael@0: }, michael@0: michael@0: _emu_cb_id: 0, michael@0: _emu_cbs: null, michael@0: runEmulatorCmd: function runEmulatorCmd(cmd, callback) { michael@0: if (callback) { michael@0: if (!this._emu_cbs) { michael@0: this._emu_cbs = {}; michael@0: } michael@0: this._emu_cbs[this._emu_cb_id] = callback; michael@0: } michael@0: this.sendToClient({emulator_cmd: cmd, id: this._emu_cb_id}, -1); michael@0: this._emu_cb_id += 1; michael@0: }, michael@0: michael@0: runEmulatorShell: function runEmulatorShell(args, callback) { michael@0: if (callback) { michael@0: if (!this._emu_cbs) { michael@0: this._emu_cbs = {}; michael@0: } michael@0: this._emu_cbs[this._emu_cb_id] = callback; michael@0: } michael@0: this.sendToClient({emulator_shell: args, id: this._emu_cb_id}, -1); michael@0: this._emu_cb_id += 1; michael@0: }, michael@0: michael@0: emulatorCmdResult: function emulatorCmdResult(message) { michael@0: if (this.context != "chrome") { michael@0: this.sendAsync("emulatorCmdResult", message, -1); michael@0: return; michael@0: } michael@0: michael@0: if (!this._emu_cbs) { michael@0: return; michael@0: } michael@0: michael@0: let cb = this._emu_cbs[message.id]; michael@0: delete this._emu_cbs[message.id]; michael@0: if (!cb) { michael@0: return; michael@0: } michael@0: try { michael@0: cb(message.result); michael@0: } michael@0: catch(e) { michael@0: this.sendError(e.message, e.code, e.stack, -1); michael@0: return; michael@0: } michael@0: }, michael@0: michael@0: importScript: function MDA_importScript(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: let converter = michael@0: Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]. michael@0: createInstance(Components.interfaces.nsIScriptableUnicodeConverter); michael@0: converter.charset = "UTF-8"; michael@0: let result = {}; michael@0: let data = converter.convertToByteArray(aRequest.parameters.script, result); michael@0: let ch = Components.classes["@mozilla.org/security/hash;1"] michael@0: .createInstance(Components.interfaces.nsICryptoHash); michael@0: ch.init(ch.MD5); michael@0: ch.update(data, data.length); michael@0: let hash = ch.finish(true); michael@0: if (this.importedScriptHashes[this.context].indexOf(hash) > -1) { michael@0: //we have already imported this script michael@0: this.sendOk(command_id); michael@0: return; michael@0: } michael@0: this.importedScriptHashes[this.context].push(hash); michael@0: if (this.context == "chrome") { michael@0: let file; michael@0: if (this.importedScripts.exists()) { michael@0: file = FileUtils.openFileOutputStream(this.importedScripts, michael@0: FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY); michael@0: } michael@0: else { michael@0: //Note: The permission bits here don't actually get set (bug 804563) michael@0: this.importedScripts.createUnique( michael@0: Components.interfaces.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8)); michael@0: file = FileUtils.openFileOutputStream(this.importedScripts, michael@0: FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE); michael@0: this.importedScripts.permissions = parseInt("0666", 8); //actually set permissions michael@0: } michael@0: file.write(aRequest.parameters.script, aRequest.parameters.script.length); michael@0: file.close(); michael@0: this.sendOk(command_id); michael@0: } michael@0: else { michael@0: this.sendAsync("importScript", michael@0: { script: aRequest.parameters.script }, michael@0: command_id); michael@0: } michael@0: }, michael@0: michael@0: clearImportedScripts: function MDA_clearImportedScripts(aRequest) { michael@0: let command_id = this.command_id = this.getCommandId(); michael@0: try { michael@0: if (this.context == "chrome") { michael@0: this.deleteFile('marionetteChromeScripts'); michael@0: } michael@0: else { michael@0: this.deleteFile('marionetteContentScripts'); michael@0: } michael@0: } michael@0: catch (e) { michael@0: this.sendError("Could not clear imported scripts", 500, e.name + ": " + e.message, command_id); michael@0: return; michael@0: } michael@0: this.sendOk(command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Takes a screenshot of a web element or the current frame. michael@0: * michael@0: * The screen capture is returned as a lossless PNG image encoded as michael@0: * a base 64 string. If the id argument is not null michael@0: * and refers to a present and visible web element's ID, the capture michael@0: * area will be limited to the bounding box of that element. michael@0: * Otherwise, the capture area will be the bounding box of the michael@0: * current frame. michael@0: * michael@0: * @param id an optional reference to a web element michael@0: * @param highlights an optional list of web elements to draw a red michael@0: * box around in the returned capture michael@0: * @return PNG image encoded as base 64 string michael@0: */ michael@0: takeScreenshot: function MDA_takeScreenshot(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: this.sendAsync("takeScreenshot", michael@0: {id: aRequest.parameters.id, michael@0: highlights: aRequest.parameters.highlights}, michael@0: this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Get the current browser orientation. michael@0: * michael@0: * Will return one of the valid primary orientation values michael@0: * portrait-primary, landscape-primary, portrait-secondary, or michael@0: * landscape-secondary. michael@0: */ michael@0: getScreenOrientation: function MDA_getScreenOrientation(aRequest) { michael@0: this.command_id = this.getCommandId(); michael@0: let curWindow = this.getCurrentWindow(); michael@0: let or = curWindow.screen.mozOrientation; michael@0: this.sendResponse(or, this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Set the current browser orientation. michael@0: * michael@0: * The supplied orientation should be given as one of the valid michael@0: * orientation values. If the orientation is unknown, an error will michael@0: * be raised. michael@0: * michael@0: * Valid orientations are "portrait" and "landscape", which fall michael@0: * back to "portrait-primary" and "landscape-primary" respectively, michael@0: * and "portrait-secondary" as well as "landscape-secondary". michael@0: */ michael@0: setScreenOrientation: function MDA_setScreenOrientation(aRequest) { michael@0: const ors = ["portrait", "landscape", michael@0: "portrait-primary", "landscape-primary", michael@0: "portrait-secondary", "landscape-secondary"]; michael@0: michael@0: this.command_id = this.getCommandId(); michael@0: let or = String(aRequest.parameters.orientation); michael@0: michael@0: let mozOr = or.toLowerCase(); michael@0: if (ors.indexOf(mozOr) < 0) { michael@0: this.sendError("Unknown screen orientation: " + or, 500, null, michael@0: this.command_id); michael@0: return; michael@0: } michael@0: michael@0: let curWindow = this.getCurrentWindow(); michael@0: if (!curWindow.screen.mozLockOrientation(mozOr)) { michael@0: this.sendError("Unable to set screen orientation: " + or, 500, michael@0: null, this.command_id); michael@0: } michael@0: this.sendOk(this.command_id); michael@0: }, michael@0: michael@0: /** michael@0: * Helper function to convert an outerWindowID into a UID that Marionette michael@0: * tracks. michael@0: */ michael@0: generateFrameId: function MDA_generateFrameId(id) { michael@0: let uid = id + (appName == "B2G" ? "-b2g" : ""); michael@0: return uid; michael@0: }, michael@0: michael@0: /** michael@0: * Receives all messages from content messageManager michael@0: */ michael@0: receiveMessage: function MDA_receiveMessage(message) { michael@0: // We need to just check if we need to remove the mozbrowserclose listener michael@0: if (this.mozBrowserClose !== null){ michael@0: let curWindow = this.getCurrentWindow(); michael@0: curWindow.removeEventListener('mozbrowserclose', this.mozBrowserClose, true); michael@0: this.mozBrowserClose = null; michael@0: } michael@0: michael@0: switch (message.name) { michael@0: case "Marionette:done": michael@0: this.sendResponse(message.json.value, message.json.command_id); michael@0: break; michael@0: case "Marionette:ok": michael@0: this.sendOk(message.json.command_id); michael@0: break; michael@0: case "Marionette:error": michael@0: this.sendError(message.json.message, message.json.status, message.json.stacktrace, message.json.command_id); michael@0: break; michael@0: case "Marionette:log": michael@0: //log server-side messages michael@0: logger.info(message.json.message); michael@0: break; michael@0: case "Marionette:shareData": michael@0: //log messages from tests michael@0: if (message.json.log) { michael@0: this.marionetteLog.addLogs(message.json.log); michael@0: } michael@0: break; michael@0: case "Marionette:runEmulatorCmd": michael@0: case "Marionette:runEmulatorShell": michael@0: this.sendToClient(message.json, -1); michael@0: break; michael@0: case "Marionette:switchToFrame": michael@0: this.curBrowser.frameManager.switchToFrame(message); michael@0: this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get(); michael@0: break; michael@0: case "Marionette:switchToModalOrigin": michael@0: this.curBrowser.frameManager.switchToModalOrigin(message); michael@0: this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get(); michael@0: break; michael@0: case "Marionette:switchedToFrame": michael@0: logger.info("Switched to frame: " + JSON.stringify(message.json)); michael@0: if (message.json.restorePrevious) { michael@0: this.currentFrameElement = this.previousFrameElement; michael@0: } michael@0: else { michael@0: if (message.json.storePrevious) { michael@0: // we don't arbitrarily save previousFrameElement, since michael@0: // we allow frame switching after modals appear, which would michael@0: // override this value and we'd lose our reference michael@0: this.previousFrameElement = this.currentFrameElement; michael@0: } michael@0: this.currentFrameElement = message.json.frameValue; michael@0: } michael@0: break; michael@0: case "Marionette:register": michael@0: // This code processes the content listener's registration information michael@0: // and either accepts the listener, or ignores it michael@0: let nullPrevious = (this.curBrowser.curFrameId == null); michael@0: let listenerWindow = michael@0: Services.wm.getOuterWindowWithId(message.json.value); michael@0: michael@0: //go in here if we're already in a remote frame. michael@0: if ((!listenerWindow || (listenerWindow.location && michael@0: listenerWindow.location.href != message.json.href)) && michael@0: (this.curBrowser.frameManager.currentRemoteFrame !== null)) { michael@0: // The outerWindowID from an OOP frame will not be meaningful to michael@0: // the parent process here, since each process maintains its own michael@0: // independent window list. So, it will either be null (!listenerWindow) michael@0: // if we're already in a remote frame, michael@0: // or it will point to some random window, which will hopefully michael@0: // cause an href mismatch. Currently this only happens michael@0: // in B2G for OOP frames registered in Marionette:switchToFrame, so michael@0: // we'll acknowledge the switchToFrame message here. michael@0: // XXX: Should have a better way of determining that this message michael@0: // is from a remote frame. michael@0: this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value); michael@0: this.sendOk(this.command_id); michael@0: } michael@0: michael@0: let browserType; michael@0: try { michael@0: browserType = message.target.getAttribute("type"); michael@0: } catch (ex) { michael@0: // browserType remains undefined. michael@0: } michael@0: let reg = {}; michael@0: // this will be sent to tell the content process if it is the main content michael@0: let mainContent = (this.curBrowser.mainContentId == null); michael@0: if (!browserType || browserType != "content") { michael@0: //curBrowser holds all the registered frames in knownFrames michael@0: reg.id = this.curBrowser.register(this.generateFrameId(message.json.value), michael@0: listenerWindow); michael@0: } michael@0: // set to true if we updated mainContentId michael@0: mainContent = ((mainContent == true) && (this.curBrowser.mainContentId != null)); michael@0: if (mainContent) { michael@0: this.mainContentFrameId = this.curBrowser.curFrameId; michael@0: } michael@0: this.curBrowser.elementManager.seenItems[reg.id] = Cu.getWeakReference(listenerWindow); michael@0: if (nullPrevious && (this.curBrowser.curFrameId != null)) { michael@0: if (!this.sendAsync("newSession", michael@0: { B2G: (appName == "B2G") }, michael@0: this.newSessionCommandId)) { michael@0: return; michael@0: } michael@0: if (this.curBrowser.newSession) { michael@0: this.getSessionCapabilities(); michael@0: this.newSessionCommandId = null; michael@0: } michael@0: } michael@0: return [reg, mainContent]; michael@0: case "Marionette:emitTouchEvent": michael@0: let globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"] michael@0: .getService(Ci.nsIMessageBroadcaster); michael@0: globalMessageManager.broadcastAsyncMessage( michael@0: "MarionetteMainListener:emitTouchEvent", message.json); michael@0: return; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: MarionetteServerConnection.prototype.requestTypes = { michael@0: "getMarionetteID": MarionetteServerConnection.prototype.getMarionetteID, michael@0: "sayHello": MarionetteServerConnection.prototype.sayHello, michael@0: "newSession": MarionetteServerConnection.prototype.newSession, michael@0: "getSessionCapabilities": MarionetteServerConnection.prototype.getSessionCapabilities, michael@0: "log": MarionetteServerConnection.prototype.log, michael@0: "getLogs": MarionetteServerConnection.prototype.getLogs, michael@0: "setContext": MarionetteServerConnection.prototype.setContext, michael@0: "executeScript": MarionetteServerConnection.prototype.execute, michael@0: "setScriptTimeout": MarionetteServerConnection.prototype.setScriptTimeout, michael@0: "timeouts": MarionetteServerConnection.prototype.timeouts, michael@0: "singleTap": MarionetteServerConnection.prototype.singleTap, michael@0: "actionChain": MarionetteServerConnection.prototype.actionChain, michael@0: "multiAction": MarionetteServerConnection.prototype.multiAction, michael@0: "executeAsyncScript": MarionetteServerConnection.prototype.executeWithCallback, michael@0: "executeJSScript": MarionetteServerConnection.prototype.executeJSScript, michael@0: "setSearchTimeout": MarionetteServerConnection.prototype.setSearchTimeout, michael@0: "findElement": MarionetteServerConnection.prototype.findElement, michael@0: "findElements": MarionetteServerConnection.prototype.findElements, michael@0: "clickElement": MarionetteServerConnection.prototype.clickElement, michael@0: "getElementAttribute": MarionetteServerConnection.prototype.getElementAttribute, michael@0: "getElementText": MarionetteServerConnection.prototype.getElementText, michael@0: "getElementTagName": MarionetteServerConnection.prototype.getElementTagName, michael@0: "isElementDisplayed": MarionetteServerConnection.prototype.isElementDisplayed, michael@0: "getElementValueOfCssProperty": MarionetteServerConnection.prototype.getElementValueOfCssProperty, michael@0: "submitElement": MarionetteServerConnection.prototype.submitElement, michael@0: "getElementSize": MarionetteServerConnection.prototype.getElementSize, michael@0: "isElementEnabled": MarionetteServerConnection.prototype.isElementEnabled, michael@0: "isElementSelected": MarionetteServerConnection.prototype.isElementSelected, michael@0: "sendKeysToElement": MarionetteServerConnection.prototype.sendKeysToElement, michael@0: "getElementLocation": MarionetteServerConnection.prototype.getElementLocation, michael@0: "getElementPosition": MarionetteServerConnection.prototype.getElementLocation, // deprecated michael@0: "clearElement": MarionetteServerConnection.prototype.clearElement, michael@0: "getTitle": MarionetteServerConnection.prototype.getTitle, michael@0: "getWindowType": MarionetteServerConnection.prototype.getWindowType, michael@0: "getPageSource": MarionetteServerConnection.prototype.getPageSource, michael@0: "get": MarionetteServerConnection.prototype.get, michael@0: "goUrl": MarionetteServerConnection.prototype.get, // deprecated michael@0: "getCurrentUrl": MarionetteServerConnection.prototype.getCurrentUrl, michael@0: "getUrl": MarionetteServerConnection.prototype.getCurrentUrl, // deprecated michael@0: "goBack": MarionetteServerConnection.prototype.goBack, michael@0: "goForward": MarionetteServerConnection.prototype.goForward, michael@0: "refresh": MarionetteServerConnection.prototype.refresh, michael@0: "getWindowHandle": MarionetteServerConnection.prototype.getWindowHandle, michael@0: "getCurrentWindowHandle": MarionetteServerConnection.prototype.getWindowHandle, // Selenium 2 compat michael@0: "getWindow": MarionetteServerConnection.prototype.getWindowHandle, // deprecated michael@0: "getWindowHandles": MarionetteServerConnection.prototype.getWindowHandles, michael@0: "getCurrentWindowHandles": MarionetteServerConnection.prototype.getWindowHandles, // Selenium 2 compat michael@0: "getWindows": MarionetteServerConnection.prototype.getWindowHandles, // deprecated michael@0: "getActiveFrame": MarionetteServerConnection.prototype.getActiveFrame, michael@0: "switchToFrame": MarionetteServerConnection.prototype.switchToFrame, michael@0: "switchToWindow": MarionetteServerConnection.prototype.switchToWindow, michael@0: "deleteSession": MarionetteServerConnection.prototype.deleteSession, michael@0: "emulatorCmdResult": MarionetteServerConnection.prototype.emulatorCmdResult, michael@0: "importScript": MarionetteServerConnection.prototype.importScript, michael@0: "clearImportedScripts": MarionetteServerConnection.prototype.clearImportedScripts, michael@0: "getAppCacheStatus": MarionetteServerConnection.prototype.getAppCacheStatus, michael@0: "close": MarionetteServerConnection.prototype.close, michael@0: "closeWindow": MarionetteServerConnection.prototype.close, // deprecated michael@0: "setTestName": MarionetteServerConnection.prototype.setTestName, michael@0: "takeScreenshot": MarionetteServerConnection.prototype.takeScreenshot, michael@0: "screenShot": MarionetteServerConnection.prototype.takeScreenshot, // deprecated michael@0: "screenshot": MarionetteServerConnection.prototype.takeScreenshot, // Selenium 2 compat michael@0: "addCookie": MarionetteServerConnection.prototype.addCookie, michael@0: "getCookies": MarionetteServerConnection.prototype.getCookies, michael@0: "getAllCookies": MarionetteServerConnection.prototype.getCookies, // deprecated michael@0: "deleteAllCookies": MarionetteServerConnection.prototype.deleteAllCookies, michael@0: "deleteCookie": MarionetteServerConnection.prototype.deleteCookie, michael@0: "getActiveElement": MarionetteServerConnection.prototype.getActiveElement, michael@0: "getScreenOrientation": MarionetteServerConnection.prototype.getScreenOrientation, michael@0: "setScreenOrientation": MarionetteServerConnection.prototype.setScreenOrientation michael@0: }; michael@0: michael@0: /** michael@0: * Creates a BrowserObj. BrowserObjs handle interactions with the michael@0: * browser, according to the current environment (desktop, b2g, etc.) michael@0: * michael@0: * @param nsIDOMWindow win michael@0: * The window whose browser needs to be accessed michael@0: */ michael@0: michael@0: function BrowserObj(win, server) { michael@0: this.DESKTOP = "desktop"; michael@0: this.B2G = "B2G"; michael@0: this.browser; michael@0: this.tab = null; //Holds a reference to the created tab, if any michael@0: this.window = win; michael@0: this.knownFrames = []; michael@0: this.curFrameId = null; michael@0: this.startPage = "about:blank"; michael@0: this.mainContentId = null; // used in B2G to identify the homescreen content page michael@0: this.newSession = true; //used to set curFrameId upon new session michael@0: this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]); michael@0: this.setBrowser(win); michael@0: this.frameManager = new FrameManager(server); //We should have one FM per BO so that we can handle modals in each Browser michael@0: michael@0: //register all message listeners michael@0: this.frameManager.addMessageManagerListeners(server.messageManager); michael@0: } michael@0: michael@0: BrowserObj.prototype = { michael@0: /** michael@0: * Set the browser if the application is not B2G michael@0: * michael@0: * @param nsIDOMWindow win michael@0: * current window reference michael@0: */ michael@0: setBrowser: function BO_setBrowser(win) { michael@0: switch (appName) { michael@0: case "Firefox": michael@0: this.browser = win.gBrowser; michael@0: break; michael@0: case "Fennec": michael@0: this.browser = win.BrowserApp; michael@0: break; michael@0: } michael@0: }, michael@0: /** michael@0: * Called when we start a session with this browser. michael@0: * michael@0: * In a desktop environment, if newTab is true, it will start michael@0: * a new 'about:blank' tab and change focus to this tab. michael@0: * michael@0: * This will also set the active messagemanager for this object michael@0: * michael@0: * @param boolean newTab michael@0: * If true, create new tab michael@0: */ michael@0: startSession: function BO_startSession(newTab, win, callback) { michael@0: if (appName != "Firefox") { michael@0: callback(win, newTab); michael@0: } michael@0: else if (newTab) { michael@0: this.tab = this.addTab(this.startPage); michael@0: //if we have a new tab, make it the selected tab michael@0: this.browser.selectedTab = this.tab; michael@0: let newTabBrowser = this.browser.getBrowserForTab(this.tab); michael@0: // wait for tab to be loaded michael@0: newTabBrowser.addEventListener("load", function onLoad() { michael@0: newTabBrowser.removeEventListener("load", onLoad, true); michael@0: callback(win, newTab); michael@0: }, true); michael@0: } michael@0: else { michael@0: //set this.tab to the currently focused tab michael@0: if (this.browser != undefined && this.browser.selectedTab != undefined) { michael@0: this.tab = this.browser.selectedTab; michael@0: } michael@0: callback(win, newTab); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Closes current tab michael@0: */ michael@0: closeTab: function BO_closeTab() { michael@0: if (this.tab != null && (appName != "B2G")) { michael@0: this.browser.removeTab(this.tab); michael@0: this.tab = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Opens a tab with given uri michael@0: * michael@0: * @param string uri michael@0: * URI to open michael@0: */ michael@0: addTab: function BO_addTab(uri) { michael@0: return this.browser.addTab(uri, true); michael@0: }, michael@0: michael@0: /** michael@0: * Loads content listeners if we don't already have them michael@0: * michael@0: * @param string script michael@0: * path of script to load michael@0: * @param nsIDOMWindow frame michael@0: * frame to load the script in michael@0: */ michael@0: loadFrameScript: function BO_loadFrameScript(script, frame) { michael@0: frame.window.messageManager.loadFrameScript(script, true, true); michael@0: Services.prefs.setBoolPref("marionette.contentListener", true); michael@0: }, michael@0: michael@0: /** michael@0: * Registers a new frame, and sets its current frame id to this frame michael@0: * if it is not already assigned, and if a) we already have a session michael@0: * or b) we're starting a new session and it is the right start frame. michael@0: * michael@0: * @param string uid michael@0: * frame uid michael@0: * @param object frameWindow michael@0: * the DOMWindow object of the frame that's being registered michael@0: */ michael@0: register: function BO_register(uid, frameWindow) { michael@0: if (this.curFrameId == null) { michael@0: // If we're setting up a new session on Firefox, we only process the michael@0: // registration for this frame if it belongs to the tab we've just michael@0: // created. michael@0: if ((!this.newSession) || michael@0: (this.newSession && michael@0: ((appName != "Firefox") || michael@0: frameWindow == this.browser.getBrowserForTab(this.tab).contentWindow))) { michael@0: this.curFrameId = uid; michael@0: this.mainContentId = uid; michael@0: } michael@0: } michael@0: this.knownFrames.push(uid); //used to delete sessions michael@0: return uid; michael@0: }, michael@0: } michael@0: michael@0: /** michael@0: * Marionette server -- this class holds a reference to a socket and creates michael@0: * MarionetteServerConnection objects as needed. michael@0: */ michael@0: this.MarionetteServer = function MarionetteServer(port, forceLocal) { michael@0: let flags = Ci.nsIServerSocket.KeepWhenOffline; michael@0: if (forceLocal) { michael@0: flags |= Ci.nsIServerSocket.LoopbackOnly; michael@0: } michael@0: let socket = new ServerSocket(port, flags, 0); michael@0: logger.info("Listening on port " + socket.port + "\n"); michael@0: socket.asyncListen(this); michael@0: this.listener = socket; michael@0: this.nextConnId = 0; michael@0: this.connections = {}; michael@0: }; michael@0: michael@0: MarionetteServer.prototype = { michael@0: onSocketAccepted: function(serverSocket, clientSocket) michael@0: { michael@0: logger.debug("accepted connection on " + clientSocket.host + ":" + clientSocket.port); michael@0: michael@0: let input = clientSocket.openInputStream(0, 0, 0); michael@0: let output = clientSocket.openOutputStream(0, 0, 0); michael@0: let aTransport = new DebuggerTransport(input, output); michael@0: let connID = "conn" + this.nextConnID++ + '.'; michael@0: let conn = new MarionetteServerConnection(connID, aTransport, this); michael@0: this.connections[connID] = conn; michael@0: michael@0: // Create a root actor for the connection and send the hello packet. michael@0: conn.sayHello(); michael@0: aTransport.ready(); michael@0: }, michael@0: michael@0: closeListener: function() { michael@0: this.listener.close(); michael@0: this.listener = null; michael@0: }, michael@0: michael@0: _connectionClosed: function DS_connectionClosed(aConnection) { michael@0: delete this.connections[aConnection.prefix]; michael@0: } michael@0: };