1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/marionette/marionette-server.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2688 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js"; 1.11 + 1.12 +// import logger 1.13 +Cu.import("resource://gre/modules/Log.jsm"); 1.14 +let logger = Log.repository.getLogger("Marionette"); 1.15 +logger.info('marionette-server.js loaded'); 1.16 + 1.17 +let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] 1.18 + .getService(Ci.mozIJSSubScriptLoader); 1.19 +loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js"); 1.20 +loader.loadSubScript("chrome://marionette/content/marionette-common.js"); 1.21 +Cu.import("resource://gre/modules/Services.jsm"); 1.22 +loader.loadSubScript("chrome://marionette/content/marionette-frame-manager.js"); 1.23 +Cu.import("chrome://marionette/content/marionette-elements.js"); 1.24 +let utils = {}; 1.25 +loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils); 1.26 +loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils); 1.27 +loader.loadSubScript("chrome://marionette/content/atoms.js", utils); 1.28 + 1.29 +let specialpowers = {}; 1.30 +loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", 1.31 + specialpowers); 1.32 +specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver(); 1.33 +specialpowers.specialPowersObserver.init(); 1.34 + 1.35 +Cu.import("resource://gre/modules/FileUtils.jsm"); 1.36 +Cu.import("resource://gre/modules/NetUtil.jsm"); 1.37 + 1.38 +Services.prefs.setBoolPref("marionette.contentListener", false); 1.39 +let appName = Services.appinfo.name; 1.40 + 1.41 +let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); 1.42 +let DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js"); 1.43 +this.DevToolsUtils = DevToolsUtils; 1.44 +loader.loadSubScript("resource://gre/modules/devtools/server/transport.js"); 1.45 + 1.46 +let bypassOffline = false; 1.47 +let qemu = "0"; 1.48 +let device = null; 1.49 + 1.50 +try { 1.51 + XPCOMUtils.defineLazyGetter(this, "libcutils", function () { 1.52 + Cu.import("resource://gre/modules/systemlibs.js"); 1.53 + return libcutils; 1.54 + }); 1.55 + if (libcutils) { 1.56 + qemu = libcutils.property_get("ro.kernel.qemu"); 1.57 + logger.info("B2G emulator: " + (qemu == "1" ? "yes" : "no")); 1.58 + device = libcutils.property_get("ro.product.device"); 1.59 + logger.info("Device detected is " + device); 1.60 + bypassOffline = (qemu == "1" || device == "panda"); 1.61 + } 1.62 +} 1.63 +catch(e) {} 1.64 + 1.65 +if (bypassOffline) { 1.66 + logger.info("Bypassing offline status."); 1.67 + Services.prefs.setBoolPref("network.gonk.manage-offline-status", false); 1.68 + Services.io.manageOfflineStatus = false; 1.69 + Services.io.offline = false; 1.70 +} 1.71 + 1.72 +// This is used to prevent newSession from returning before the telephony 1.73 +// API's are ready; see bug 792647. This assumes that marionette-server.js 1.74 +// will be loaded before the 'system-message-listener-ready' message 1.75 +// is fired. If this stops being true, this approach will have to change. 1.76 +let systemMessageListenerReady = false; 1.77 +Services.obs.addObserver(function() { 1.78 + systemMessageListenerReady = true; 1.79 +}, "system-message-listener-ready", false); 1.80 + 1.81 +/* 1.82 + * Custom exceptions 1.83 + */ 1.84 +function FrameSendNotInitializedError(frame) { 1.85 + this.code = 54; 1.86 + this.frame = frame; 1.87 + this.message = "Error sending message to frame (NS_ERROR_NOT_INITIALIZED)"; 1.88 + this.toString = function() { 1.89 + return this.message + " " + this.frame + "; frame has closed."; 1.90 + } 1.91 +} 1.92 + 1.93 +function FrameSendFailureError(frame) { 1.94 + this.code = 55; 1.95 + this.frame = frame; 1.96 + this.message = "Error sending message to frame (NS_ERROR_FAILURE)"; 1.97 + this.toString = function() { 1.98 + return this.message + " " + this.frame + "; frame not responding."; 1.99 + } 1.100 +} 1.101 + 1.102 +/** 1.103 + * The server connection is responsible for all marionette API calls. It gets created 1.104 + * for each connection and manages all chrome and browser based calls. It 1.105 + * mediates content calls by issuing appropriate messages to the content process. 1.106 + */ 1.107 +function MarionetteServerConnection(aPrefix, aTransport, aServer) 1.108 +{ 1.109 + this.uuidGen = Cc["@mozilla.org/uuid-generator;1"] 1.110 + .getService(Ci.nsIUUIDGenerator); 1.111 + 1.112 + this.prefix = aPrefix; 1.113 + this.server = aServer; 1.114 + this.conn = aTransport; 1.115 + this.conn.hooks = this; 1.116 + 1.117 + // marionette uses a protocol based on the debugger server, which requires 1.118 + // passing back "actor ids" with responses. unlike the debugger server, 1.119 + // we don't have multiple actors, so just use a dummy value of "0" here 1.120 + this.actorID = "0"; 1.121 + 1.122 + this.globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"] 1.123 + .getService(Ci.nsIMessageBroadcaster); 1.124 + this.messageManager = this.globalMessageManager; 1.125 + this.browsers = {}; //holds list of BrowserObjs 1.126 + this.curBrowser = null; // points to current browser 1.127 + this.context = "content"; 1.128 + this.scriptTimeout = null; 1.129 + this.searchTimeout = null; 1.130 + this.pageTimeout = null; 1.131 + this.timer = null; 1.132 + this.inactivityTimer = null; 1.133 + this.heartbeatCallback = function () {}; // called by simpletest methods 1.134 + this.marionetteLog = new MarionetteLogObj(); 1.135 + this.command_id = null; 1.136 + this.mainFrame = null; //topmost chrome frame 1.137 + this.curFrame = null; // chrome iframe that currently has focus 1.138 + this.mainContentFrameId = null; 1.139 + this.importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']); 1.140 + this.importedScriptHashes = {"chrome" : [], "content": []}; 1.141 + this.currentFrameElement = null; 1.142 + this.testName = null; 1.143 + this.mozBrowserClose = null; 1.144 +} 1.145 + 1.146 +MarionetteServerConnection.prototype = { 1.147 + 1.148 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener, 1.149 + Ci.nsIObserver, 1.150 + Ci.nsISupportsWeakReference]), 1.151 + 1.152 + /** 1.153 + * Debugger transport callbacks: 1.154 + */ 1.155 + onPacket: function MSC_onPacket(aPacket) { 1.156 + // Dispatch the request 1.157 + if (this.requestTypes && this.requestTypes[aPacket.name]) { 1.158 + try { 1.159 + this.requestTypes[aPacket.name].bind(this)(aPacket); 1.160 + } catch(e) { 1.161 + this.conn.send({ error: ("error occurred while processing '" + 1.162 + aPacket.name), 1.163 + message: e.message }); 1.164 + } 1.165 + } else { 1.166 + this.conn.send({ error: "unrecognizedPacketType", 1.167 + message: ('Marionette does not ' + 1.168 + 'recognize the packet type "' + 1.169 + aPacket.name + '"') }); 1.170 + } 1.171 + }, 1.172 + 1.173 + onClosed: function MSC_onClosed(aStatus) { 1.174 + this.server._connectionClosed(this); 1.175 + this.sessionTearDown(); 1.176 + }, 1.177 + 1.178 + /** 1.179 + * Helper methods: 1.180 + */ 1.181 + 1.182 + /** 1.183 + * Switches to the global ChromeMessageBroadcaster, potentially replacing a frame-specific 1.184 + * ChromeMessageSender. Has no effect if the global ChromeMessageBroadcaster is already 1.185 + * in use. If this replaces a frame-specific ChromeMessageSender, it removes the message 1.186 + * listeners from that sender, and then puts the corresponding frame script "to sleep", 1.187 + * which removes most of the message listeners from it as well. 1.188 + */ 1.189 + switchToGlobalMessageManager: function MDA_switchToGlobalMM() { 1.190 + if (this.curBrowser && this.curBrowser.frameManager.currentRemoteFrame !== null) { 1.191 + this.curBrowser.frameManager.removeMessageManagerListeners(this.messageManager); 1.192 + this.sendAsync("sleepSession", null, null, true); 1.193 + this.curBrowser.frameManager.currentRemoteFrame = null; 1.194 + } 1.195 + this.messageManager = this.globalMessageManager; 1.196 + }, 1.197 + 1.198 + /** 1.199 + * Helper method to send async messages to the content listener 1.200 + * 1.201 + * @param string name 1.202 + * Suffix of the targetted message listener (Marionette:<suffix>) 1.203 + * @param object values 1.204 + * Object to send to the listener 1.205 + */ 1.206 + sendAsync: function MDA_sendAsync(name, values, commandId, ignoreFailure) { 1.207 + let success = true; 1.208 + if (values instanceof Object && commandId) { 1.209 + values.command_id = commandId; 1.210 + } 1.211 + if (this.curBrowser.frameManager.currentRemoteFrame !== null) { 1.212 + try { 1.213 + this.messageManager.sendAsyncMessage( 1.214 + "Marionette:" + name + this.curBrowser.frameManager.currentRemoteFrame.targetFrameId, values); 1.215 + } 1.216 + catch(e) { 1.217 + if (!ignoreFailure) { 1.218 + success = false; 1.219 + let error = e; 1.220 + switch(e.result) { 1.221 + case Components.results.NS_ERROR_FAILURE: 1.222 + error = new FrameSendFailureError(this.curBrowser.frameManager.currentRemoteFrame); 1.223 + break; 1.224 + case Components.results.NS_ERROR_NOT_INITIALIZED: 1.225 + error = new FrameSendNotInitializedError(this.curBrowser.frameManager.currentRemoteFrame); 1.226 + break; 1.227 + default: 1.228 + break; 1.229 + } 1.230 + let code = error.hasOwnProperty('code') ? e.code : 500; 1.231 + this.sendError(error.toString(), code, error.stack, commandId); 1.232 + } 1.233 + } 1.234 + } 1.235 + else { 1.236 + this.messageManager.broadcastAsyncMessage( 1.237 + "Marionette:" + name + this.curBrowser.curFrameId, values); 1.238 + } 1.239 + return success; 1.240 + }, 1.241 + 1.242 + logRequest: function MDA_logRequest(type, data) { 1.243 + logger.debug("Got request: " + type + ", data: " + JSON.stringify(data) + ", id: " + this.command_id); 1.244 + }, 1.245 + 1.246 + /** 1.247 + * Generic method to pass a response to the client 1.248 + * 1.249 + * @param object msg 1.250 + * Response to send back to client 1.251 + * @param string command_id 1.252 + * Unique identifier assigned to the client's request. 1.253 + * Used to distinguish the asynchronous responses. 1.254 + */ 1.255 + sendToClient: function MDA_sendToClient(msg, command_id) { 1.256 + logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id + 1.257 + ", " + this.command_id); 1.258 + if (!command_id) { 1.259 + logger.warn("got a response with no command_id"); 1.260 + return; 1.261 + } 1.262 + else if (command_id != -1) { 1.263 + // A command_id of -1 is used for emulator callbacks, and those 1.264 + // don't use this.command_id. 1.265 + if (!this.command_id) { 1.266 + // A null value for this.command_id means we've already processed 1.267 + // a message for the previous value, and so the current message is a 1.268 + // duplicate. 1.269 + logger.warn("ignoring duplicate response for command_id " + command_id); 1.270 + return; 1.271 + } 1.272 + else if (this.command_id != command_id) { 1.273 + logger.warn("ignoring out-of-sync response"); 1.274 + return; 1.275 + } 1.276 + } 1.277 + this.conn.send(msg); 1.278 + if (command_id != -1) { 1.279 + // Don't unset this.command_id if this message is to process an 1.280 + // emulator callback, since another response for this command_id is 1.281 + // expected, after the containing call to execute_async_script finishes. 1.282 + this.command_id = null; 1.283 + } 1.284 + }, 1.285 + 1.286 + /** 1.287 + * Send a value to client 1.288 + * 1.289 + * @param object value 1.290 + * Value to send back to client 1.291 + * @param string command_id 1.292 + * Unique identifier assigned to the client's request. 1.293 + * Used to distinguish the asynchronous responses. 1.294 + */ 1.295 + sendResponse: function MDA_sendResponse(value, command_id) { 1.296 + if (typeof(value) == 'undefined') 1.297 + value = null; 1.298 + this.sendToClient({from:this.actorID, value: value}, command_id); 1.299 + }, 1.300 + 1.301 + sayHello: function MDA_sayHello() { 1.302 + this.conn.send({ from: "root", 1.303 + applicationType: "gecko", 1.304 + traits: [] }); 1.305 + }, 1.306 + 1.307 + getMarionetteID: function MDA_getMarionette() { 1.308 + this.conn.send({ "from": "root", "id": this.actorID }); 1.309 + }, 1.310 + 1.311 + /** 1.312 + * Send ack to client 1.313 + * 1.314 + * @param string command_id 1.315 + * Unique identifier assigned to the client's request. 1.316 + * Used to distinguish the asynchronous responses. 1.317 + */ 1.318 + sendOk: function MDA_sendOk(command_id) { 1.319 + this.sendToClient({from:this.actorID, ok: true}, command_id); 1.320 + }, 1.321 + 1.322 + /** 1.323 + * Send error message to client 1.324 + * 1.325 + * @param string message 1.326 + * Error message 1.327 + * @param number status 1.328 + * Status number 1.329 + * @param string trace 1.330 + * Stack trace 1.331 + * @param string command_id 1.332 + * Unique identifier assigned to the client's request. 1.333 + * Used to distinguish the asynchronous responses. 1.334 + */ 1.335 + sendError: function MDA_sendError(message, status, trace, command_id) { 1.336 + let error_msg = {message: message, status: status, stacktrace: trace}; 1.337 + this.sendToClient({from:this.actorID, error: error_msg}, command_id); 1.338 + }, 1.339 + 1.340 + /** 1.341 + * Gets the current active window 1.342 + * 1.343 + * @return nsIDOMWindow 1.344 + */ 1.345 + getCurrentWindow: function MDA_getCurrentWindow() { 1.346 + let type = null; 1.347 + if (this.curFrame == null) { 1.348 + if (this.curBrowser == null) { 1.349 + if (this.context == "content") { 1.350 + type = 'navigator:browser'; 1.351 + } 1.352 + return Services.wm.getMostRecentWindow(type); 1.353 + } 1.354 + else { 1.355 + return this.curBrowser.window; 1.356 + } 1.357 + } 1.358 + else { 1.359 + return this.curFrame; 1.360 + } 1.361 + }, 1.362 + 1.363 + /** 1.364 + * Gets the the window enumerator 1.365 + * 1.366 + * @return nsISimpleEnumerator 1.367 + */ 1.368 + getWinEnumerator: function MDA_getWinEnumerator() { 1.369 + let type = null; 1.370 + if (appName != "B2G" && this.context == "content") { 1.371 + type = 'navigator:browser'; 1.372 + } 1.373 + return Services.wm.getEnumerator(type); 1.374 + }, 1.375 + 1.376 + /** 1.377 + * Create a new BrowserObj for window and add to known browsers 1.378 + * 1.379 + * @param nsIDOMWindow win 1.380 + * Window for which we will create a BrowserObj 1.381 + * 1.382 + * @return string 1.383 + * Returns the unique server-assigned ID of the window 1.384 + */ 1.385 + addBrowser: function MDA_addBrowser(win) { 1.386 + let browser = new BrowserObj(win, this); 1.387 + let winId = win.QueryInterface(Ci.nsIInterfaceRequestor). 1.388 + getInterface(Ci.nsIDOMWindowUtils).outerWindowID; 1.389 + winId = winId + ((appName == "B2G") ? '-b2g' : ''); 1.390 + this.browsers[winId] = browser; 1.391 + this.curBrowser = this.browsers[winId]; 1.392 + if (this.curBrowser.elementManager.seenItems[winId] == undefined) { 1.393 + //add this to seenItems so we can guarantee the user will get winId as this window's id 1.394 + this.curBrowser.elementManager.seenItems[winId] = Cu.getWeakReference(win); 1.395 + } 1.396 + }, 1.397 + 1.398 + /** 1.399 + * Start a new session in a new browser. 1.400 + * 1.401 + * If newSession is true, we will switch focus to the start frame 1.402 + * when it registers. Also, if it is in desktop, then a new tab 1.403 + * with the start page uri (about:blank) will be opened. 1.404 + * 1.405 + * @param nsIDOMWindow win 1.406 + * Window whose browser we need to access 1.407 + * @param boolean newSession 1.408 + * True if this is the first time we're talking to this browser 1.409 + */ 1.410 + startBrowser: function MDA_startBrowser(win, newSession) { 1.411 + this.mainFrame = win; 1.412 + this.curFrame = null; 1.413 + this.addBrowser(win); 1.414 + this.curBrowser.newSession = newSession; 1.415 + this.curBrowser.startSession(newSession, win, this.whenBrowserStarted.bind(this)); 1.416 + }, 1.417 + 1.418 + /** 1.419 + * Callback invoked after a new session has been started in a browser. 1.420 + * Loads the Marionette frame script into the browser if needed. 1.421 + * 1.422 + * @param nsIDOMWindow win 1.423 + * Window whose browser we need to access 1.424 + * @param boolean newSession 1.425 + * True if this is the first time we're talking to this browser 1.426 + */ 1.427 + whenBrowserStarted: function MDA_whenBrowserStarted(win, newSession) { 1.428 + try { 1.429 + if (!Services.prefs.getBoolPref("marionette.contentListener") || !newSession) { 1.430 + this.curBrowser.loadFrameScript(FRAME_SCRIPT, win); 1.431 + } 1.432 + } 1.433 + catch (e) { 1.434 + //there may not always be a content process 1.435 + logger.info("could not load listener into content for page: " + win.location.href); 1.436 + } 1.437 + utils.window = win; 1.438 + }, 1.439 + 1.440 + /** 1.441 + * Recursively get all labeled text 1.442 + * 1.443 + * @param nsIDOMElement el 1.444 + * The parent element 1.445 + * @param array lines 1.446 + * Array that holds the text lines 1.447 + */ 1.448 + getVisibleText: function MDA_getVisibleText(el, lines) { 1.449 + let nodeName = el.nodeName; 1.450 + try { 1.451 + if (utils.isElementDisplayed(el)) { 1.452 + if (el.value) { 1.453 + lines.push(el.value); 1.454 + } 1.455 + for (var child in el.childNodes) { 1.456 + this.getVisibleText(el.childNodes[child], lines); 1.457 + }; 1.458 + } 1.459 + } 1.460 + catch (e) { 1.461 + if (nodeName == "#text") { 1.462 + lines.push(el.textContent); 1.463 + } 1.464 + } 1.465 + }, 1.466 + 1.467 + getCommandId: function MDA_getCommandId() { 1.468 + return this.uuidGen.generateUUID().toString(); 1.469 + }, 1.470 + 1.471 + /** 1.472 + * Given a file name, this will delete the file from the temp directory if it exists 1.473 + */ 1.474 + deleteFile: function(filename) { 1.475 + let file = FileUtils.getFile('TmpD', [filename.toString()]); 1.476 + if (file.exists()) { 1.477 + file.remove(true); 1.478 + } 1.479 + }, 1.480 + 1.481 + /** 1.482 + * Marionette API: 1.483 + * 1.484 + * All methods implementing a command from the client should create a 1.485 + * command_id, and then use this command_id in all messages exchanged with 1.486 + * the frame scripts and with responses sent to the client. This prevents 1.487 + * commands and responses from getting out-of-sync, which can happen in 1.488 + * the case of execute_async calls that timeout and then later send a 1.489 + * response, and other situations. See bug 779011. See setScriptTimeout() 1.490 + * for a basic example. 1.491 + */ 1.492 + 1.493 + /** 1.494 + * Create a new session. This creates a new BrowserObj. 1.495 + * 1.496 + * In a desktop environment, this opens a new browser with 1.497 + * "about:blank" which subsequent commands will be sent to. 1.498 + * 1.499 + * This will send a hash map of supported capabilities to the client 1.500 + * as part of the Marionette:register IPC command in the 1.501 + * receiveMessage callback when a new browser is created. 1.502 + */ 1.503 + newSession: function MDA_newSession() { 1.504 + this.command_id = this.getCommandId(); 1.505 + this.newSessionCommandId = this.command_id; 1.506 + 1.507 + this.scriptTimeout = 10000; 1.508 + 1.509 + function waitForWindow() { 1.510 + let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.511 + let win = this.getCurrentWindow(); 1.512 + if (!win || 1.513 + (appName == "Firefox" && !win.gBrowser) || 1.514 + (appName == "Fennec" && !win.BrowserApp)) { 1.515 + checkTimer.initWithCallback(waitForWindow.bind(this), 100, 1.516 + Ci.nsITimer.TYPE_ONE_SHOT); 1.517 + } 1.518 + else { 1.519 + this.startBrowser(win, true); 1.520 + } 1.521 + } 1.522 + 1.523 + if (!Services.prefs.getBoolPref("marionette.contentListener")) { 1.524 + waitForWindow.call(this); 1.525 + } 1.526 + else if ((appName != "Firefox") && (this.curBrowser == null)) { 1.527 + // If there is a content listener, then we just wake it up 1.528 + this.addBrowser(this.getCurrentWindow()); 1.529 + this.curBrowser.startSession(false, this.getCurrentWindow(), 1.530 + this.whenBrowserStarted); 1.531 + this.messageManager.broadcastAsyncMessage("Marionette:restart", {}); 1.532 + } 1.533 + else { 1.534 + this.sendError("Session already running", 500, null, 1.535 + this.command_id); 1.536 + } 1.537 + this.switchToGlobalMessageManager(); 1.538 + }, 1.539 + 1.540 + /** 1.541 + * Send the current session's capabilities to the client. 1.542 + * 1.543 + * Capabilities informs the client of which WebDriver features are 1.544 + * supported by Firefox and Marionette. They are immutable for the 1.545 + * length of the session. 1.546 + * 1.547 + * The return value is an immutable map of string keys 1.548 + * ("capabilities") to values, which may be of types boolean, 1.549 + * numerical or string. 1.550 + */ 1.551 + getSessionCapabilities: function MDA_getSessionCapabilities() { 1.552 + this.command_id = this.getCommandId(); 1.553 + 1.554 + let isB2G = appName == "B2G"; 1.555 + let platformName = Services.appinfo.OS.toUpperCase(); 1.556 + 1.557 + let caps = { 1.558 + // Mandated capabilities 1.559 + "browserName": appName, 1.560 + "browserVersion": Services.appinfo.version, 1.561 + "platformName": platformName, 1.562 + "platformVersion": Services.appinfo.platformVersion, 1.563 + 1.564 + // Supported features 1.565 + "cssSelectorsEnabled": true, 1.566 + "handlesAlerts": false, 1.567 + "javascriptEnabled": true, 1.568 + "nativeEvents": false, 1.569 + "rotatable": isB2G, 1.570 + "secureSsl": false, 1.571 + "takesElementScreenshot": true, 1.572 + "takesScreenshot": true, 1.573 + 1.574 + // Selenium 2 compat 1.575 + "platform": platformName, 1.576 + 1.577 + // Proprietary extensions 1.578 + "XULappId" : Services.appinfo.ID, 1.579 + "appBuildId" : Services.appinfo.appBuildID, 1.580 + "device": qemu == "1" ? "qemu" : (!device ? "desktop" : device), 1.581 + "version": Services.appinfo.version 1.582 + }; 1.583 + 1.584 + // eideticker (bug 965297) and mochitest (bug 965304) 1.585 + // compatibility. They only check for the presence of this 1.586 + // property and should so not be in caps if not on a B2G device. 1.587 + if (isB2G) 1.588 + caps.b2g = true; 1.589 + 1.590 + this.sendResponse(caps, this.command_id); 1.591 + }, 1.592 + 1.593 + /** 1.594 + * Log message. Accepts user defined log-level. 1.595 + * 1.596 + * @param object aRequest 1.597 + * 'value' member holds log message 1.598 + * 'level' member hold log level 1.599 + */ 1.600 + log: function MDA_log(aRequest) { 1.601 + this.command_id = this.getCommandId(); 1.602 + this.marionetteLog.log(aRequest.parameters.value, aRequest.parameters.level); 1.603 + this.sendOk(this.command_id); 1.604 + }, 1.605 + 1.606 + /** 1.607 + * Return all logged messages. 1.608 + */ 1.609 + getLogs: function MDA_getLogs() { 1.610 + this.command_id = this.getCommandId(); 1.611 + this.sendResponse(this.marionetteLog.getLogs(), this.command_id); 1.612 + }, 1.613 + 1.614 + /** 1.615 + * Sets the context of the subsequent commands to be either 'chrome' or 'content' 1.616 + * 1.617 + * @param object aRequest 1.618 + * 'value' member holds the name of the context to be switched to 1.619 + */ 1.620 + setContext: function MDA_setContext(aRequest) { 1.621 + this.command_id = this.getCommandId(); 1.622 + this.logRequest("setContext", aRequest); 1.623 + let context = aRequest.parameters.value; 1.624 + if (context != "content" && context != "chrome") { 1.625 + this.sendError("invalid context", 500, null, this.command_id); 1.626 + } 1.627 + else { 1.628 + this.context = context; 1.629 + this.sendOk(this.command_id); 1.630 + } 1.631 + }, 1.632 + 1.633 + /** 1.634 + * Returns a chrome sandbox that can be used by the execute_foo functions. 1.635 + * 1.636 + * @param nsIDOMWindow aWindow 1.637 + * Window in which we will execute code 1.638 + * @param Marionette marionette 1.639 + * Marionette test instance 1.640 + * @param object args 1.641 + * Client given args 1.642 + * @return Sandbox 1.643 + * Returns the sandbox 1.644 + */ 1.645 + createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args, specialPowers, command_id) { 1.646 + try { 1.647 + args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow); 1.648 + } 1.649 + catch(e) { 1.650 + this.sendError(e.message, e.code, e.stack, command_id); 1.651 + return; 1.652 + } 1.653 + 1.654 + let _chromeSandbox = new Cu.Sandbox(aWindow, 1.655 + { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''}); 1.656 + _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args); 1.657 + _chromeSandbox.__marionetteParams = args; 1.658 + _chromeSandbox.testUtils = utils; 1.659 + 1.660 + marionette.exports.forEach(function(fn) { 1.661 + try { 1.662 + _chromeSandbox[fn] = marionette[fn].bind(marionette); 1.663 + } 1.664 + catch(e) { 1.665 + _chromeSandbox[fn] = marionette[fn]; 1.666 + } 1.667 + }); 1.668 + 1.669 + _chromeSandbox.isSystemMessageListenerReady = 1.670 + function() { return systemMessageListenerReady; } 1.671 + 1.672 + if (specialPowers == true) { 1.673 + loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js", 1.674 + _chromeSandbox); 1.675 + loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js", 1.676 + _chromeSandbox); 1.677 + loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js", 1.678 + _chromeSandbox); 1.679 + } 1.680 + 1.681 + return _chromeSandbox; 1.682 + }, 1.683 + 1.684 + /** 1.685 + * Executes a script in the given sandbox. 1.686 + * 1.687 + * @param Sandbox sandbox 1.688 + * Sandbox in which the script will run 1.689 + * @param string script 1.690 + * The script to run 1.691 + * @param boolean directInject 1.692 + * If true, then the script will be run as is, 1.693 + * and not as a function body (as you would 1.694 + * do using the WebDriver spec) 1.695 + * @param boolean async 1.696 + * True if the script is asynchronous 1.697 + */ 1.698 + executeScriptInSandbox: function MDA_executeScriptInSandbox(sandbox, script, 1.699 + directInject, async, command_id, timeout) { 1.700 + 1.701 + if (directInject && async && 1.702 + (timeout == null || timeout == 0)) { 1.703 + this.sendError("Please set a timeout", 21, null, command_id); 1.704 + return; 1.705 + } 1.706 + 1.707 + if (this.importedScripts.exists()) { 1.708 + let stream = Cc["@mozilla.org/network/file-input-stream;1"]. 1.709 + createInstance(Ci.nsIFileInputStream); 1.710 + stream.init(this.importedScripts, -1, 0, 0); 1.711 + let data = NetUtil.readInputStreamToString(stream, stream.available()); 1.712 + script = data + script; 1.713 + } 1.714 + 1.715 + let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0); 1.716 + 1.717 + if (directInject && !async && 1.718 + (res == undefined || res.passed == undefined)) { 1.719 + this.sendError("finish() not called", 500, null, command_id); 1.720 + return; 1.721 + } 1.722 + 1.723 + if (!async) { 1.724 + this.sendResponse(this.curBrowser.elementManager.wrapValue(res), 1.725 + command_id); 1.726 + } 1.727 + }, 1.728 + 1.729 + /** 1.730 + * Execute the given script either as a function body (executeScript) 1.731 + * or directly (for 'mochitest' like JS Marionette tests) 1.732 + * 1.733 + * @param object aRequest 1.734 + * 'script' member is the script to run 1.735 + * 'args' member holds the arguments to the script 1.736 + * @param boolean directInject 1.737 + * if true, it will be run directly and not as a 1.738 + * function body 1.739 + */ 1.740 + execute: function MDA_execute(aRequest, directInject) { 1.741 + let inactivityTimeout = aRequest.parameters.inactivityTimeout; 1.742 + let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout; 1.743 + let command_id = this.command_id = this.getCommandId(); 1.744 + let script; 1.745 + this.logRequest("execute", aRequest); 1.746 + if (aRequest.parameters.newSandbox == undefined) { 1.747 + //if client does not send a value in newSandbox, 1.748 + //then they expect the same behaviour as webdriver 1.749 + aRequest.parameters.newSandbox = true; 1.750 + } 1.751 + if (this.context == "content") { 1.752 + this.sendAsync("executeScript", 1.753 + { 1.754 + script: aRequest.parameters.script, 1.755 + args: aRequest.parameters.args, 1.756 + newSandbox: aRequest.parameters.newSandbox, 1.757 + timeout: timeout, 1.758 + specialPowers: aRequest.parameters.specialPowers, 1.759 + filename: aRequest.parameters.filename, 1.760 + line: aRequest.parameters.line 1.761 + }, 1.762 + command_id); 1.763 + return; 1.764 + } 1.765 + 1.766 + // handle the inactivity timeout 1.767 + let that = this; 1.768 + if (inactivityTimeout) { 1.769 + let inactivityTimeoutHandler = function(message, status) { 1.770 + let error_msg = {message: value, status: status}; 1.771 + that.sendToClient({from: that.actorID, error: error_msg}, 1.772 + marionette.command_id); 1.773 + }; 1.774 + let setTimer = function() { 1.775 + that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.776 + if (that.inactivityTimer != null) { 1.777 + that.inactivityTimer.initWithCallback(function() { 1.778 + inactivityTimeoutHandler("timed out due to inactivity", 28); 1.779 + }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT); 1.780 + } 1.781 + } 1.782 + setTimer(); 1.783 + this.heartbeatCallback = function resetInactivityTimer() { 1.784 + that.inactivityTimer.cancel(); 1.785 + setTimer(); 1.786 + } 1.787 + } 1.788 + 1.789 + let curWindow = this.getCurrentWindow(); 1.790 + let marionette = new Marionette(this, curWindow, "chrome", 1.791 + this.marionetteLog, 1.792 + timeout, this.heartbeatCallback, this.testName); 1.793 + let _chromeSandbox = this.createExecuteSandbox(curWindow, 1.794 + marionette, 1.795 + aRequest.parameters.args, 1.796 + aRequest.parameters.specialPowers, 1.797 + command_id); 1.798 + if (!_chromeSandbox) 1.799 + return; 1.800 + 1.801 + try { 1.802 + _chromeSandbox.finish = function chromeSandbox_finish() { 1.803 + if (that.inactivityTimer != null) { 1.804 + that.inactivityTimer.cancel(); 1.805 + } 1.806 + return marionette.generate_results(); 1.807 + }; 1.808 + 1.809 + if (directInject) { 1.810 + script = aRequest.parameters.script; 1.811 + } 1.812 + else { 1.813 + script = "let func = function() {" + 1.814 + aRequest.parameters.script + 1.815 + "};" + 1.816 + "func.apply(null, __marionetteParams);"; 1.817 + } 1.818 + this.executeScriptInSandbox(_chromeSandbox, script, directInject, 1.819 + false, command_id, timeout); 1.820 + } 1.821 + catch (e) { 1.822 + let error = createStackMessage(e, 1.823 + "execute_script", 1.824 + aRequest.parameters.filename, 1.825 + aRequest.parameters.line, 1.826 + script); 1.827 + this.sendError(error[0], 17, error[1], command_id); 1.828 + } 1.829 + }, 1.830 + 1.831 + /** 1.832 + * Set the timeout for asynchronous script execution 1.833 + * 1.834 + * @param object aRequest 1.835 + * 'ms' member is time in milliseconds to set timeout 1.836 + */ 1.837 + setScriptTimeout: function MDA_setScriptTimeout(aRequest) { 1.838 + this.command_id = this.getCommandId(); 1.839 + let timeout = parseInt(aRequest.parameters.ms); 1.840 + if(isNaN(timeout)){ 1.841 + this.sendError("Not a Number", 500, null, this.command_id); 1.842 + } 1.843 + else { 1.844 + this.scriptTimeout = timeout; 1.845 + this.sendOk(this.command_id); 1.846 + } 1.847 + }, 1.848 + 1.849 + /** 1.850 + * execute pure JS script. Used to execute 'mochitest'-style Marionette tests. 1.851 + * 1.852 + * @param object aRequest 1.853 + * 'script' member holds the script to execute 1.854 + * 'args' member holds the arguments to the script 1.855 + * 'timeout' member will be used as the script timeout if it is given 1.856 + */ 1.857 + executeJSScript: function MDA_executeJSScript(aRequest) { 1.858 + let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout; 1.859 + let command_id = this.command_id = this.getCommandId(); 1.860 + 1.861 + //all pure JS scripts will need to call Marionette.finish() to complete the test. 1.862 + if (aRequest.newSandbox == undefined) { 1.863 + //if client does not send a value in newSandbox, 1.864 + //then they expect the same behaviour as webdriver 1.865 + aRequest.newSandbox = true; 1.866 + } 1.867 + if (this.context == "chrome") { 1.868 + if (aRequest.parameters.async) { 1.869 + this.executeWithCallback(aRequest, aRequest.parameters.async); 1.870 + } 1.871 + else { 1.872 + this.execute(aRequest, true); 1.873 + } 1.874 + } 1.875 + else { 1.876 + this.sendAsync("executeJSScript", 1.877 + { 1.878 + script: aRequest.parameters.script, 1.879 + args: aRequest.parameters.args, 1.880 + newSandbox: aRequest.parameters.newSandbox, 1.881 + async: aRequest.parameters.async, 1.882 + timeout: timeout, 1.883 + inactivityTimeout: aRequest.parameters.inactivityTimeout, 1.884 + specialPowers: aRequest.parameters.specialPowers, 1.885 + filename: aRequest.parameters.filename, 1.886 + line: aRequest.parameters.line, 1.887 + }, 1.888 + command_id); 1.889 + } 1.890 + }, 1.891 + 1.892 + /** 1.893 + * This function is used by executeAsync and executeJSScript to execute a script 1.894 + * in a sandbox. 1.895 + * 1.896 + * For executeJSScript, it will return a message only when the finish() method is called. 1.897 + * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] 1.898 + * method is called, or if it times out. 1.899 + * 1.900 + * @param object aRequest 1.901 + * 'script' member holds the script to execute 1.902 + * 'args' member holds the arguments for the script 1.903 + * @param boolean directInject 1.904 + * if true, it will be run directly and not as a 1.905 + * function body 1.906 + */ 1.907 + executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) { 1.908 + let inactivityTimeout = aRequest.parameters.inactivityTimeout; 1.909 + let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout; 1.910 + let command_id = this.command_id = this.getCommandId(); 1.911 + let script; 1.912 + this.logRequest("executeWithCallback", aRequest); 1.913 + if (aRequest.parameters.newSandbox == undefined) { 1.914 + //if client does not send a value in newSandbox, 1.915 + //then they expect the same behaviour as webdriver 1.916 + aRequest.parameters.newSandbox = true; 1.917 + } 1.918 + 1.919 + if (this.context == "content") { 1.920 + this.sendAsync("executeAsyncScript", 1.921 + { 1.922 + script: aRequest.parameters.script, 1.923 + args: aRequest.parameters.args, 1.924 + id: this.command_id, 1.925 + newSandbox: aRequest.parameters.newSandbox, 1.926 + timeout: timeout, 1.927 + inactivityTimeout: inactivityTimeout, 1.928 + specialPowers: aRequest.parameters.specialPowers, 1.929 + filename: aRequest.parameters.filename, 1.930 + line: aRequest.parameters.line 1.931 + }, 1.932 + command_id); 1.933 + return; 1.934 + } 1.935 + 1.936 + // handle the inactivity timeout 1.937 + let that = this; 1.938 + if (inactivityTimeout) { 1.939 + this.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.940 + if (this.inactivityTimer != null) { 1.941 + this.inactivityTimer.initWithCallback(function() { 1.942 + chromeAsyncReturnFunc("timed out due to inactivity", 28); 1.943 + }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT); 1.944 + } 1.945 + this.heartbeatCallback = function resetInactivityTimer() { 1.946 + that.inactivityTimer.cancel(); 1.947 + that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.948 + if (that.inactivityTimer != null) { 1.949 + that.inactivityTimer.initWithCallback(function() { 1.950 + chromeAsyncReturnFunc("timed out due to inactivity", 28); 1.951 + }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT); 1.952 + } 1.953 + } 1.954 + } 1.955 + 1.956 + let curWindow = this.getCurrentWindow(); 1.957 + let original_onerror = curWindow.onerror; 1.958 + let that = this; 1.959 + that.timeout = timeout; 1.960 + let marionette = new Marionette(this, curWindow, "chrome", 1.961 + this.marionetteLog, 1.962 + timeout, this.heartbeatCallback, this.testName); 1.963 + marionette.command_id = this.command_id; 1.964 + 1.965 + function chromeAsyncReturnFunc(value, status, stacktrace) { 1.966 + if (that._emu_cbs && Object.keys(that._emu_cbs).length) { 1.967 + value = "Emulator callback still pending when finish() called"; 1.968 + status = 500; 1.969 + that._emu_cbs = null; 1.970 + } 1.971 + 1.972 + if (value == undefined) 1.973 + value = null; 1.974 + if (that.command_id == marionette.command_id) { 1.975 + if (that.timer != null) { 1.976 + that.timer.cancel(); 1.977 + that.timer = null; 1.978 + } 1.979 + 1.980 + curWindow.onerror = original_onerror; 1.981 + 1.982 + if (status == 0 || status == undefined) { 1.983 + that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status}, 1.984 + marionette.command_id); 1.985 + } 1.986 + else { 1.987 + let error_msg = {message: value, status: status, stacktrace: stacktrace}; 1.988 + that.sendToClient({from: that.actorID, error: error_msg}, 1.989 + marionette.command_id); 1.990 + } 1.991 + } 1.992 + if (that.inactivityTimer != null) { 1.993 + that.inactivityTimer.cancel(); 1.994 + } 1.995 + } 1.996 + 1.997 + curWindow.onerror = function (errorMsg, url, lineNumber) { 1.998 + chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17); 1.999 + return true; 1.1000 + }; 1.1001 + 1.1002 + function chromeAsyncFinish() { 1.1003 + chromeAsyncReturnFunc(marionette.generate_results(), 0); 1.1004 + } 1.1005 + 1.1006 + let _chromeSandbox = this.createExecuteSandbox(curWindow, 1.1007 + marionette, 1.1008 + aRequest.parameters.args, 1.1009 + aRequest.parameters.specialPowers, 1.1010 + command_id); 1.1011 + if (!_chromeSandbox) 1.1012 + return; 1.1013 + 1.1014 + try { 1.1015 + 1.1016 + this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.1017 + if (this.timer != null) { 1.1018 + this.timer.initWithCallback(function() { 1.1019 + chromeAsyncReturnFunc("timed out", 28); 1.1020 + }, that.timeout, Ci.nsITimer.TYPE_ONESHOT); 1.1021 + } 1.1022 + 1.1023 + _chromeSandbox.returnFunc = chromeAsyncReturnFunc; 1.1024 + _chromeSandbox.finish = chromeAsyncFinish; 1.1025 + 1.1026 + if (directInject) { 1.1027 + script = aRequest.parameters.script; 1.1028 + } 1.1029 + else { 1.1030 + script = '__marionetteParams.push(returnFunc);' 1.1031 + + 'let marionetteScriptFinished = returnFunc;' 1.1032 + + 'let __marionetteFunc = function() {' + aRequest.parameters.script + '};' 1.1033 + + '__marionetteFunc.apply(null, __marionetteParams);'; 1.1034 + } 1.1035 + 1.1036 + this.executeScriptInSandbox(_chromeSandbox, script, directInject, 1.1037 + true, command_id, timeout); 1.1038 + } catch (e) { 1.1039 + let error = createStackMessage(e, 1.1040 + "execute_async_script", 1.1041 + aRequest.parameters.filename, 1.1042 + aRequest.parameters.line, 1.1043 + script); 1.1044 + chromeAsyncReturnFunc(error[0], 17, error[1]); 1.1045 + } 1.1046 + }, 1.1047 + 1.1048 + /** 1.1049 + * Navigate to to given URL. 1.1050 + * 1.1051 + * This will follow redirects issued by the server. When the method 1.1052 + * returns is based on the page load strategy that the user has 1.1053 + * selected. 1.1054 + * 1.1055 + * Documents that contain a META tag with the "http-equiv" attribute 1.1056 + * set to "refresh" will return if the timeout is greater than 1 1.1057 + * second and the other criteria for determining whether a page is 1.1058 + * loaded are met. When the refresh period is 1 second or less and 1.1059 + * the page load strategy is "normal" or "conservative", it will 1.1060 + * wait for the page to complete loading before returning. 1.1061 + * 1.1062 + * If any modal dialog box, such as those opened on 1.1063 + * window.onbeforeunload or window.alert, is opened at any point in 1.1064 + * the page load, it will return immediately. 1.1065 + * 1.1066 + * If a 401 response is seen by the browser, it will return 1.1067 + * immediately. That is, if BASIC, DIGEST, NTLM or similar 1.1068 + * authentication is required, the page load is assumed to be 1.1069 + * complete. This does not include FORM-based authentication. 1.1070 + * 1.1071 + * @param object aRequest where <code>url</code> property holds the 1.1072 + * URL to navigate to 1.1073 + */ 1.1074 + get: function MDA_get(aRequest) { 1.1075 + let command_id = this.command_id = this.getCommandId(); 1.1076 + if (this.context != "chrome") { 1.1077 + aRequest.command_id = command_id; 1.1078 + aRequest.parameters.pageTimeout = this.pageTimeout; 1.1079 + this.sendAsync("get", aRequest.parameters, command_id); 1.1080 + return; 1.1081 + } 1.1082 + 1.1083 + this.getCurrentWindow().location.href = aRequest.parameters.url; 1.1084 + let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.1085 + let start = new Date().getTime(); 1.1086 + let end = null; 1.1087 + 1.1088 + function checkLoad() { 1.1089 + end = new Date().getTime(); 1.1090 + let elapse = end - start; 1.1091 + if (this.pageTimeout == null || elapse <= this.pageTimeout){ 1.1092 + if (curWindow.document.readyState == "complete") { 1.1093 + sendOk(command_id); 1.1094 + return; 1.1095 + } 1.1096 + else{ 1.1097 + checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); 1.1098 + } 1.1099 + } 1.1100 + else{ 1.1101 + sendError("Error loading page", 13, null, command_id); 1.1102 + return; 1.1103 + } 1.1104 + } 1.1105 + checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); 1.1106 + }, 1.1107 + 1.1108 + /** 1.1109 + * Get a string representing the current URL. 1.1110 + * 1.1111 + * On Desktop this returns a string representation of the URL of the 1.1112 + * current top level browsing context. This is equivalent to 1.1113 + * document.location.href. 1.1114 + * 1.1115 + * When in the context of the chrome, this returns the canonical URL 1.1116 + * of the current resource. 1.1117 + */ 1.1118 + getCurrentUrl: function MDA_getCurrentUrl() { 1.1119 + this.command_id = this.getCommandId(); 1.1120 + if (this.context == "chrome") { 1.1121 + this.sendResponse(this.getCurrentWindow().location.href, this.command_id); 1.1122 + } 1.1123 + else { 1.1124 + this.sendAsync("getCurrentUrl", {}, this.command_id); 1.1125 + } 1.1126 + }, 1.1127 + 1.1128 + /** 1.1129 + * Gets the current title of the window 1.1130 + */ 1.1131 + getTitle: function MDA_getTitle() { 1.1132 + this.command_id = this.getCommandId(); 1.1133 + if (this.context == "chrome"){ 1.1134 + var curWindow = this.getCurrentWindow(); 1.1135 + var title = curWindow.document.documentElement.getAttribute('title'); 1.1136 + this.sendResponse(title, this.command_id); 1.1137 + } 1.1138 + else { 1.1139 + this.sendAsync("getTitle", {}, this.command_id); 1.1140 + } 1.1141 + }, 1.1142 + 1.1143 + /** 1.1144 + * Gets the current type of the window 1.1145 + */ 1.1146 + getWindowType: function MDA_getWindowType() { 1.1147 + this.command_id = this.getCommandId(); 1.1148 + var curWindow = this.getCurrentWindow(); 1.1149 + var type = curWindow.document.documentElement.getAttribute('windowtype'); 1.1150 + this.sendResponse(type, this.command_id); 1.1151 + }, 1.1152 + 1.1153 + /** 1.1154 + * Gets the page source of the content document 1.1155 + */ 1.1156 + getPageSource: function MDA_getPageSource(){ 1.1157 + this.command_id = this.getCommandId(); 1.1158 + if (this.context == "chrome"){ 1.1159 + let curWindow = this.getCurrentWindow(); 1.1160 + let XMLSerializer = curWindow.XMLSerializer; 1.1161 + let pageSource = new XMLSerializer().serializeToString(curWindow.document); 1.1162 + this.sendResponse(pageSource, this.command_id); 1.1163 + } 1.1164 + else { 1.1165 + this.sendAsync("getPageSource", {}, this.command_id); 1.1166 + } 1.1167 + }, 1.1168 + 1.1169 + /** 1.1170 + * Go back in history 1.1171 + */ 1.1172 + goBack: function MDA_goBack() { 1.1173 + this.command_id = this.getCommandId(); 1.1174 + this.sendAsync("goBack", {}, this.command_id); 1.1175 + }, 1.1176 + 1.1177 + /** 1.1178 + * Go forward in history 1.1179 + */ 1.1180 + goForward: function MDA_goForward() { 1.1181 + this.command_id = this.getCommandId(); 1.1182 + this.sendAsync("goForward", {}, this.command_id); 1.1183 + }, 1.1184 + 1.1185 + /** 1.1186 + * Refresh the page 1.1187 + */ 1.1188 + refresh: function MDA_refresh() { 1.1189 + this.command_id = this.getCommandId(); 1.1190 + this.sendAsync("refresh", {}, this.command_id); 1.1191 + }, 1.1192 + 1.1193 + /** 1.1194 + * Get the current window's handle. 1.1195 + * 1.1196 + * Return an opaque server-assigned identifier to this window that 1.1197 + * uniquely identifies it within this Marionette instance. This can 1.1198 + * be used to switch to this window at a later point. 1.1199 + * 1.1200 + * @return unique window handle (string) 1.1201 + */ 1.1202 + getWindowHandle: function MDA_getWindowHandle() { 1.1203 + this.command_id = this.getCommandId(); 1.1204 + for (let i in this.browsers) { 1.1205 + if (this.curBrowser == this.browsers[i]) { 1.1206 + this.sendResponse(i, this.command_id); 1.1207 + return; 1.1208 + } 1.1209 + } 1.1210 + }, 1.1211 + 1.1212 + /** 1.1213 + * Get list of windows in the current context. 1.1214 + * 1.1215 + * If called in the content context it will return a list of 1.1216 + * references to all available browser windows. Called in the 1.1217 + * chrome context, it will list all available windows, not just 1.1218 + * browser windows (e.g. not just navigator.browser). 1.1219 + * 1.1220 + * Each window handle is assigned by the server, and the array of 1.1221 + * strings returned does not have a guaranteed ordering. 1.1222 + * 1.1223 + * @return unordered array of unique window handles as strings 1.1224 + */ 1.1225 + getWindowHandles: function MDA_getWindowHandles() { 1.1226 + this.command_id = this.getCommandId(); 1.1227 + let res = []; 1.1228 + let winEn = this.getWinEnumerator(); 1.1229 + while (winEn.hasMoreElements()) { 1.1230 + let foundWin = winEn.getNext(); 1.1231 + let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor) 1.1232 + .getInterface(Ci.nsIDOMWindowUtils).outerWindowID; 1.1233 + winId = winId + ((appName == "B2G") ? "-b2g" : ""); 1.1234 + res.push(winId); 1.1235 + } 1.1236 + this.sendResponse(res, this.command_id); 1.1237 + }, 1.1238 + 1.1239 + /** 1.1240 + * Switch to a window based on name or server-assigned id. 1.1241 + * Searches based on name, then id. 1.1242 + * 1.1243 + * @param object aRequest 1.1244 + * 'name' member holds the name or id of the window to switch to 1.1245 + */ 1.1246 + switchToWindow: function MDA_switchToWindow(aRequest) { 1.1247 + let command_id = this.command_id = this.getCommandId(); 1.1248 + let winEn = this.getWinEnumerator(); 1.1249 + while(winEn.hasMoreElements()) { 1.1250 + let foundWin = winEn.getNext(); 1.1251 + let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor) 1.1252 + .getInterface(Ci.nsIDOMWindowUtils) 1.1253 + .outerWindowID; 1.1254 + winId = winId + ((appName == "B2G") ? '-b2g' : ''); 1.1255 + if (aRequest.parameters.name == foundWin.name || aRequest.parameters.name == winId) { 1.1256 + if (this.browsers[winId] == undefined) { 1.1257 + //enable Marionette in that browser window 1.1258 + this.startBrowser(foundWin, false); 1.1259 + } 1.1260 + else { 1.1261 + utils.window = foundWin; 1.1262 + this.curBrowser = this.browsers[winId]; 1.1263 + } 1.1264 + this.sendOk(command_id); 1.1265 + return; 1.1266 + } 1.1267 + } 1.1268 + this.sendError("Unable to locate window " + aRequest.parameters.name, 23, null, 1.1269 + command_id); 1.1270 + }, 1.1271 + 1.1272 + getActiveFrame: function MDA_getActiveFrame() { 1.1273 + this.command_id = this.getCommandId(); 1.1274 + 1.1275 + if (this.context == "chrome") { 1.1276 + if (this.curFrame) { 1.1277 + let frameUid = this.curBrowser.elementManager.addToKnownElements(this.curFrame.frameElement); 1.1278 + this.sendResponse(frameUid, this.command_id); 1.1279 + } else { 1.1280 + // no current frame, we're at toplevel 1.1281 + this.sendResponse(null, this.command_id); 1.1282 + } 1.1283 + } else { 1.1284 + // not chrome 1.1285 + this.sendResponse(this.currentFrameElement, this.command_id); 1.1286 + } 1.1287 + }, 1.1288 + 1.1289 + /** 1.1290 + * Switch to a given frame within the current window 1.1291 + * 1.1292 + * @param object aRequest 1.1293 + * 'element' is the element to switch to 1.1294 + * 'id' if element is not set, then this 1.1295 + * holds either the id, name or index 1.1296 + * of the frame to switch to 1.1297 + */ 1.1298 + switchToFrame: function MDA_switchToFrame(aRequest) { 1.1299 + let command_id = this.command_id = this.getCommandId(); 1.1300 + this.logRequest("switchToFrame", aRequest); 1.1301 + let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.1302 + let curWindow = this.getCurrentWindow(); 1.1303 + let checkLoad = function() { 1.1304 + let errorRegex = /about:.+(error)|(blocked)\?/; 1.1305 + let curWindow = this.getCurrentWindow(); 1.1306 + if (curWindow.document.readyState == "complete") { 1.1307 + this.sendOk(command_id); 1.1308 + return; 1.1309 + } 1.1310 + else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)) { 1.1311 + this.sendError("Error loading page", 13, null, command_id); 1.1312 + return; 1.1313 + } 1.1314 + 1.1315 + checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); 1.1316 + } 1.1317 + if (this.context == "chrome") { 1.1318 + let foundFrame = null; 1.1319 + if ((aRequest.parameters.id == null) && (aRequest.parameters.element == null)) { 1.1320 + this.curFrame = null; 1.1321 + if (aRequest.parameters.focus) { 1.1322 + this.mainFrame.focus(); 1.1323 + } 1.1324 + checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); 1.1325 + return; 1.1326 + } 1.1327 + if (aRequest.parameters.element != undefined) { 1.1328 + if (this.curBrowser.elementManager.seenItems[aRequest.parameters.element]) { 1.1329 + let wantedFrame = this.curBrowser.elementManager.getKnownElement(aRequest.parameters.element, curWindow); //HTMLIFrameElement 1.1330 + let frames = curWindow.document.getElementsByTagName("iframe"); 1.1331 + let numFrames = frames.length; 1.1332 + for (let i = 0; i < numFrames; i++) { 1.1333 + if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) { 1.1334 + curWindow = frames[i].contentWindow; 1.1335 + this.curFrame = curWindow; 1.1336 + if (aRequest.parameters.focus) { 1.1337 + this.curFrame.focus(); 1.1338 + } 1.1339 + checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); 1.1340 + return; 1.1341 + } 1.1342 + } 1.1343 + } 1.1344 + } 1.1345 + switch(typeof(aRequest.parameters.id)) { 1.1346 + case "string" : 1.1347 + let foundById = null; 1.1348 + let frames = curWindow.document.getElementsByTagName("iframe"); 1.1349 + let numFrames = frames.length; 1.1350 + for (let i = 0; i < numFrames; i++) { 1.1351 + //give precedence to name 1.1352 + let frame = frames[i]; 1.1353 + if (frame.getAttribute("name") == aRequest.parameters.id) { 1.1354 + foundFrame = i; 1.1355 + curWindow = frame.contentWindow; 1.1356 + break; 1.1357 + } else if ((foundById == null) && (frame.id == aRequest.parameters.id)) { 1.1358 + foundById = i; 1.1359 + } 1.1360 + } 1.1361 + if ((foundFrame == null) && (foundById != null)) { 1.1362 + foundFrame = foundById; 1.1363 + curWindow = frames[foundById].contentWindow; 1.1364 + } 1.1365 + break; 1.1366 + case "number": 1.1367 + if (curWindow.frames[aRequest.parameters.id] != undefined) { 1.1368 + foundFrame = aRequest.parameters.id; 1.1369 + curWindow = curWindow.frames[foundFrame].frameElement.contentWindow; 1.1370 + } 1.1371 + break; 1.1372 + } 1.1373 + if (foundFrame != null) { 1.1374 + this.curFrame = curWindow; 1.1375 + if (aRequest.parameters.focus) { 1.1376 + this.curFrame.focus(); 1.1377 + } 1.1378 + checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT); 1.1379 + } else { 1.1380 + this.sendError("Unable to locate frame: " + aRequest.parameters.id, 8, null, 1.1381 + command_id); 1.1382 + } 1.1383 + } 1.1384 + else { 1.1385 + if ((!aRequest.parameters.id) && (!aRequest.parameters.element) && 1.1386 + (this.curBrowser.frameManager.currentRemoteFrame !== null)) { 1.1387 + // We're currently using a ChromeMessageSender for a remote frame, so this 1.1388 + // request indicates we need to switch back to the top-level (parent) frame. 1.1389 + // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so 1.1390 + // we send the message to the right listener. 1.1391 + this.switchToGlobalMessageManager(); 1.1392 + } 1.1393 + aRequest.command_id = command_id; 1.1394 + this.sendAsync("switchToFrame", aRequest.parameters, command_id); 1.1395 + } 1.1396 + }, 1.1397 + 1.1398 + /** 1.1399 + * Set timeout for searching for elements 1.1400 + * 1.1401 + * @param object aRequest 1.1402 + * 'ms' holds the search timeout in milliseconds 1.1403 + */ 1.1404 + setSearchTimeout: function MDA_setSearchTimeout(aRequest) { 1.1405 + this.command_id = this.getCommandId(); 1.1406 + let timeout = parseInt(aRequest.parameters.ms); 1.1407 + if (isNaN(timeout)) { 1.1408 + this.sendError("Not a Number", 500, null, this.command_id); 1.1409 + } 1.1410 + else { 1.1411 + this.searchTimeout = timeout; 1.1412 + this.sendOk(this.command_id); 1.1413 + } 1.1414 + }, 1.1415 + 1.1416 + /** 1.1417 + * Set timeout for page loading, searching and scripts 1.1418 + * 1.1419 + * @param object aRequest 1.1420 + * 'type' hold the type of timeout 1.1421 + * 'ms' holds the timeout in milliseconds 1.1422 + */ 1.1423 + timeouts: function MDA_timeouts(aRequest){ 1.1424 + /*setTimeout*/ 1.1425 + this.command_id = this.getCommandId(); 1.1426 + let timeout_type = aRequest.parameters.type; 1.1427 + let timeout = parseInt(aRequest.parameters.ms); 1.1428 + if (isNaN(timeout)) { 1.1429 + this.sendError("Not a Number", 500, null, this.command_id); 1.1430 + } 1.1431 + else { 1.1432 + if (timeout_type == "implicit") { 1.1433 + this.setSearchTimeout(aRequest); 1.1434 + } 1.1435 + else if (timeout_type == "script") { 1.1436 + this.setScriptTimeout(aRequest); 1.1437 + } 1.1438 + else { 1.1439 + this.pageTimeout = timeout; 1.1440 + this.sendOk(this.command_id); 1.1441 + } 1.1442 + } 1.1443 + }, 1.1444 + 1.1445 + /** 1.1446 + * Single Tap 1.1447 + * 1.1448 + * @param object aRequest 1.1449 + 'element' represents the ID of the element to single tap on 1.1450 + */ 1.1451 + singleTap: function MDA_singleTap(aRequest) { 1.1452 + this.command_id = this.getCommandId(); 1.1453 + let serId = aRequest.parameters.id; 1.1454 + let x = aRequest.parameters.x; 1.1455 + let y = aRequest.parameters.y; 1.1456 + if (this.context == "chrome") { 1.1457 + this.sendError("Command 'singleTap' is not available in chrome context", 500, null, this.command_id); 1.1458 + } 1.1459 + else { 1.1460 + this.sendAsync("singleTap", 1.1461 + { 1.1462 + id: serId, 1.1463 + corx: x, 1.1464 + cory: y 1.1465 + }, 1.1466 + this.command_id); 1.1467 + } 1.1468 + }, 1.1469 + 1.1470 + /** 1.1471 + * actionChain 1.1472 + * 1.1473 + * @param object aRequest 1.1474 + * 'value' represents a nested array: inner array represents each event; outer array represents collection of events 1.1475 + */ 1.1476 + actionChain: function MDA_actionChain(aRequest) { 1.1477 + this.command_id = this.getCommandId(); 1.1478 + if (this.context == "chrome") { 1.1479 + this.sendError("Command 'actionChain' is not available in chrome context", 500, null, this.command_id); 1.1480 + } 1.1481 + else { 1.1482 + this.sendAsync("actionChain", 1.1483 + { 1.1484 + chain: aRequest.parameters.chain, 1.1485 + nextId: aRequest.parameters.nextId 1.1486 + }, 1.1487 + this.command_id); 1.1488 + } 1.1489 + }, 1.1490 + 1.1491 + /** 1.1492 + * multiAction 1.1493 + * 1.1494 + * @param object aRequest 1.1495 + * 'value' represents a nested array: inner array represents each event; 1.1496 + * middle array represents collection of events for each finger 1.1497 + * outer array represents all the fingers 1.1498 + */ 1.1499 + 1.1500 + multiAction: function MDA_multiAction(aRequest) { 1.1501 + this.command_id = this.getCommandId(); 1.1502 + if (this.context == "chrome") { 1.1503 + this.sendError("Command 'multiAction' is not available in chrome context", 500, null, this.command_id); 1.1504 + } 1.1505 + else { 1.1506 + this.sendAsync("multiAction", 1.1507 + { 1.1508 + value: aRequest.parameters.value, 1.1509 + maxlen: aRequest.parameters.max_length 1.1510 + }, 1.1511 + this.command_id); 1.1512 + } 1.1513 + }, 1.1514 + 1.1515 + /** 1.1516 + * Find an element using the indicated search strategy. 1.1517 + * 1.1518 + * @param object aRequest 1.1519 + * 'using' member indicates which search method to use 1.1520 + * 'value' member is the value the client is looking for 1.1521 + */ 1.1522 + findElement: function MDA_findElement(aRequest) { 1.1523 + let command_id = this.command_id = this.getCommandId(); 1.1524 + if (this.context == "chrome") { 1.1525 + let id; 1.1526 + try { 1.1527 + let on_success = this.sendResponse.bind(this); 1.1528 + let on_error = this.sendError.bind(this); 1.1529 + id = this.curBrowser.elementManager.find( 1.1530 + this.getCurrentWindow(), 1.1531 + aRequest.parameters, 1.1532 + this.searchTimeout, 1.1533 + on_success, 1.1534 + on_error, 1.1535 + false, 1.1536 + command_id); 1.1537 + } 1.1538 + catch (e) { 1.1539 + this.sendError(e.message, e.code, e.stack, command_id); 1.1540 + return; 1.1541 + } 1.1542 + } 1.1543 + else { 1.1544 + this.sendAsync("findElementContent", 1.1545 + { 1.1546 + value: aRequest.parameters.value, 1.1547 + using: aRequest.parameters.using, 1.1548 + element: aRequest.parameters.element, 1.1549 + searchTimeout: this.searchTimeout 1.1550 + }, 1.1551 + command_id); 1.1552 + } 1.1553 + }, 1.1554 + 1.1555 + /** 1.1556 + * Find elements using the indicated search strategy. 1.1557 + * 1.1558 + * @param object aRequest 1.1559 + * 'using' member indicates which search method to use 1.1560 + * 'value' member is the value the client is looking for 1.1561 + */ 1.1562 + findElements: function MDA_findElements(aRequest) { 1.1563 + let command_id = this.command_id = this.getCommandId(); 1.1564 + if (this.context == "chrome") { 1.1565 + let id; 1.1566 + try { 1.1567 + let on_success = this.sendResponse.bind(this); 1.1568 + let on_error = this.sendError.bind(this); 1.1569 + id = this.curBrowser.elementManager.find(this.getCurrentWindow(), 1.1570 + aRequest.parameters, 1.1571 + this.searchTimeout, 1.1572 + on_success, 1.1573 + on_error, 1.1574 + true, 1.1575 + command_id); 1.1576 + } 1.1577 + catch (e) { 1.1578 + this.sendError(e.message, e.code, e.stack, command_id); 1.1579 + return; 1.1580 + } 1.1581 + } 1.1582 + else { 1.1583 + this.sendAsync("findElementsContent", 1.1584 + { 1.1585 + value: aRequest.parameters.value, 1.1586 + using: aRequest.parameters.using, 1.1587 + element: aRequest.parameters.element, 1.1588 + searchTimeout: this.searchTimeout 1.1589 + }, 1.1590 + command_id); 1.1591 + } 1.1592 + }, 1.1593 + 1.1594 + /** 1.1595 + * Return the active element on the page 1.1596 + */ 1.1597 + getActiveElement: function MDA_getActiveElement(){ 1.1598 + let command_id = this.command_id = this.getCommandId(); 1.1599 + this.sendAsync("getActiveElement", {}, command_id); 1.1600 + }, 1.1601 + 1.1602 + /** 1.1603 + * Send click event to element 1.1604 + * 1.1605 + * @param object aRequest 1.1606 + * 'id' member holds the reference id to 1.1607 + * the element that will be clicked 1.1608 + */ 1.1609 + clickElement: function MDA_clickElementent(aRequest) { 1.1610 + let command_id = this.command_id = this.getCommandId(); 1.1611 + if (this.context == "chrome") { 1.1612 + try { 1.1613 + //NOTE: click atom fails, fall back to click() action 1.1614 + let el = this.curBrowser.elementManager.getKnownElement( 1.1615 + aRequest.parameters.id, this.getCurrentWindow()); 1.1616 + el.click(); 1.1617 + this.sendOk(command_id); 1.1618 + } 1.1619 + catch (e) { 1.1620 + this.sendError(e.message, e.code, e.stack, command_id); 1.1621 + } 1.1622 + } 1.1623 + else { 1.1624 + // We need to protect against the click causing an OOP frame to close. 1.1625 + // This fires the mozbrowserclose event when it closes so we need to 1.1626 + // listen for it and then just send an error back. The person making the 1.1627 + // call should be aware something isnt right and handle accordingly 1.1628 + let curWindow = this.getCurrentWindow(); 1.1629 + let self = this; 1.1630 + this.mozBrowserClose = function() { 1.1631 + curWindow.removeEventListener('mozbrowserclose', self.mozBrowserClose, true); 1.1632 + self.switchToGlobalMessageManager(); 1.1633 + self.sendError("The frame closed during the click, recovering to allow further communications", 500, null, command_id); 1.1634 + }; 1.1635 + curWindow.addEventListener('mozbrowserclose', this.mozBrowserClose, true); 1.1636 + this.sendAsync("clickElement", 1.1637 + { id: aRequest.parameters.id }, 1.1638 + command_id); 1.1639 + } 1.1640 + }, 1.1641 + 1.1642 + /** 1.1643 + * Get a given attribute of an element 1.1644 + * 1.1645 + * @param object aRequest 1.1646 + * 'id' member holds the reference id to 1.1647 + * the element that will be inspected 1.1648 + * 'name' member holds the name of the attribute to retrieve 1.1649 + */ 1.1650 + getElementAttribute: function MDA_getElementAttribute(aRequest) { 1.1651 + let command_id = this.command_id = this.getCommandId(); 1.1652 + if (this.context == "chrome") { 1.1653 + try { 1.1654 + let el = this.curBrowser.elementManager.getKnownElement( 1.1655 + aRequest.parameters.id, this.getCurrentWindow()); 1.1656 + this.sendResponse(utils.getElementAttribute(el, aRequest.parameters.name), 1.1657 + command_id); 1.1658 + } 1.1659 + catch (e) { 1.1660 + this.sendError(e.message, e.code, e.stack, command_id); 1.1661 + } 1.1662 + } 1.1663 + else { 1.1664 + this.sendAsync("getElementAttribute", 1.1665 + { 1.1666 + id: aRequest.parameters.id, 1.1667 + name: aRequest.parameters.name 1.1668 + }, 1.1669 + command_id); 1.1670 + } 1.1671 + }, 1.1672 + 1.1673 + /** 1.1674 + * Get the text of an element, if any. Includes the text of all child elements. 1.1675 + * 1.1676 + * @param object aRequest 1.1677 + * 'id' member holds the reference id to 1.1678 + * the element that will be inspected 1.1679 + */ 1.1680 + getElementText: function MDA_getElementText(aRequest) { 1.1681 + let command_id = this.command_id = this.getCommandId(); 1.1682 + if (this.context == "chrome") { 1.1683 + //Note: for chrome, we look at text nodes, and any node with a "label" field 1.1684 + try { 1.1685 + let el = this.curBrowser.elementManager.getKnownElement( 1.1686 + aRequest.parameters.id, this.getCurrentWindow()); 1.1687 + let lines = []; 1.1688 + this.getVisibleText(el, lines); 1.1689 + lines = lines.join("\n"); 1.1690 + this.sendResponse(lines, command_id); 1.1691 + } 1.1692 + catch (e) { 1.1693 + this.sendError(e.message, e.code, e.stack, command_id); 1.1694 + } 1.1695 + } 1.1696 + else { 1.1697 + this.sendAsync("getElementText", 1.1698 + { id: aRequest.parameters.id }, 1.1699 + command_id); 1.1700 + } 1.1701 + }, 1.1702 + 1.1703 + /** 1.1704 + * Get the tag name of the element. 1.1705 + * 1.1706 + * @param object aRequest 1.1707 + * 'id' member holds the reference id to 1.1708 + * the element that will be inspected 1.1709 + */ 1.1710 + getElementTagName: function MDA_getElementTagName(aRequest) { 1.1711 + let command_id = this.command_id = this.getCommandId(); 1.1712 + if (this.context == "chrome") { 1.1713 + try { 1.1714 + let el = this.curBrowser.elementManager.getKnownElement( 1.1715 + aRequest.parameters.id, this.getCurrentWindow()); 1.1716 + this.sendResponse(el.tagName.toLowerCase(), command_id); 1.1717 + } 1.1718 + catch (e) { 1.1719 + this.sendError(e.message, e.code, e.stack, command_id); 1.1720 + } 1.1721 + } 1.1722 + else { 1.1723 + this.sendAsync("getElementTagName", 1.1724 + { id: aRequest.parameters.id }, 1.1725 + command_id); 1.1726 + } 1.1727 + }, 1.1728 + 1.1729 + /** 1.1730 + * Check if element is displayed 1.1731 + * 1.1732 + * @param object aRequest 1.1733 + * 'id' member holds the reference id to 1.1734 + * the element that will be checked 1.1735 + */ 1.1736 + isElementDisplayed: function MDA_isElementDisplayed(aRequest) { 1.1737 + let command_id = this.command_id = this.getCommandId(); 1.1738 + if (this.context == "chrome") { 1.1739 + try { 1.1740 + let el = this.curBrowser.elementManager.getKnownElement( 1.1741 + aRequest.parameters.id, this.getCurrentWindow()); 1.1742 + this.sendResponse(utils.isElementDisplayed(el), command_id); 1.1743 + } 1.1744 + catch (e) { 1.1745 + this.sendError(e.message, e.code, e.stack, command_id); 1.1746 + } 1.1747 + } 1.1748 + else { 1.1749 + this.sendAsync("isElementDisplayed", 1.1750 + { id:aRequest.parameters.id }, 1.1751 + command_id); 1.1752 + } 1.1753 + }, 1.1754 + 1.1755 + /** 1.1756 + * Return the property of the computed style of an element 1.1757 + * 1.1758 + * @param object aRequest 1.1759 + * 'id' member holds the reference id to 1.1760 + * the element that will be checked 1.1761 + * 'propertyName' is the CSS rule that is being requested 1.1762 + */ 1.1763 + getElementValueOfCssProperty: function MDA_getElementValueOfCssProperty(aRequest){ 1.1764 + let command_id = this.command_id = this.getCommandId(); 1.1765 + this.sendAsync("getElementValueOfCssProperty", 1.1766 + {id: aRequest.parameters.id, propertyName: aRequest.parameters.propertyName}, 1.1767 + command_id); 1.1768 + }, 1.1769 + 1.1770 + /** 1.1771 + * Submit a form on a content page by either using form or element in a form 1.1772 + * @param object aRequest 1.1773 + * 'id' member holds the reference id to 1.1774 + * the element that will be checked 1.1775 + */ 1.1776 + submitElement: function MDA_submitElement(aRequest) { 1.1777 + let command_id = this.command_id = this.getCommandId(); 1.1778 + if (this.context == "chrome") { 1.1779 + this.sendError("Command 'submitElement' is not available in chrome context", 500, null, this.command_id); 1.1780 + } 1.1781 + else { 1.1782 + this.sendAsync("submitElement", {id: aRequest.parameters.id}, command_id); 1.1783 + } 1.1784 + }, 1.1785 + 1.1786 + /** 1.1787 + * Check if element is enabled 1.1788 + * 1.1789 + * @param object aRequest 1.1790 + * 'id' member holds the reference id to 1.1791 + * the element that will be checked 1.1792 + */ 1.1793 + isElementEnabled: function MDA_isElementEnabled(aRequest) { 1.1794 + let command_id = this.command_id = this.getCommandId(); 1.1795 + if (this.context == "chrome") { 1.1796 + try { 1.1797 + //Selenium atom doesn't quite work here 1.1798 + let el = this.curBrowser.elementManager.getKnownElement( 1.1799 + aRequest.parameters.id, this.getCurrentWindow()); 1.1800 + if (el.disabled != undefined) { 1.1801 + this.sendResponse(!!!el.disabled, command_id); 1.1802 + } 1.1803 + else { 1.1804 + this.sendResponse(true, command_id); 1.1805 + } 1.1806 + } 1.1807 + catch (e) { 1.1808 + this.sendError(e.message, e.code, e.stack, command_id); 1.1809 + } 1.1810 + } 1.1811 + else { 1.1812 + this.sendAsync("isElementEnabled", 1.1813 + { id:aRequest.parameters.id }, 1.1814 + command_id); 1.1815 + } 1.1816 + }, 1.1817 + 1.1818 + /** 1.1819 + * Check if element is selected 1.1820 + * 1.1821 + * @param object aRequest 1.1822 + * 'id' member holds the reference id to 1.1823 + * the element that will be checked 1.1824 + */ 1.1825 + isElementSelected: function MDA_isElementSelected(aRequest) { 1.1826 + let command_id = this.command_id = this.getCommandId(); 1.1827 + if (this.context == "chrome") { 1.1828 + try { 1.1829 + //Selenium atom doesn't quite work here 1.1830 + let el = this.curBrowser.elementManager.getKnownElement( 1.1831 + aRequest.parameters.id, this.getCurrentWindow()); 1.1832 + if (el.checked != undefined) { 1.1833 + this.sendResponse(!!el.checked, command_id); 1.1834 + } 1.1835 + else if (el.selected != undefined) { 1.1836 + this.sendResponse(!!el.selected, command_id); 1.1837 + } 1.1838 + else { 1.1839 + this.sendResponse(true, command_id); 1.1840 + } 1.1841 + } 1.1842 + catch (e) { 1.1843 + this.sendError(e.message, e.code, e.stack, command_id); 1.1844 + } 1.1845 + } 1.1846 + else { 1.1847 + this.sendAsync("isElementSelected", 1.1848 + { id:aRequest.parameters.id }, 1.1849 + command_id); 1.1850 + } 1.1851 + }, 1.1852 + 1.1853 + getElementSize: function MDA_getElementSize(aRequest) { 1.1854 + let command_id = this.command_id = this.getCommandId(); 1.1855 + if (this.context == "chrome") { 1.1856 + try { 1.1857 + let el = this.curBrowser.elementManager.getKnownElement( 1.1858 + aRequest.parameters.id, this.getCurrentWindow()); 1.1859 + let clientRect = el.getBoundingClientRect(); 1.1860 + this.sendResponse({width: clientRect.width, height: clientRect.height}, 1.1861 + command_id); 1.1862 + } 1.1863 + catch (e) { 1.1864 + this.sendError(e.message, e.code, e.stack, command_id); 1.1865 + } 1.1866 + } 1.1867 + else { 1.1868 + this.sendAsync("getElementSize", 1.1869 + { id:aRequest.parameters.id }, 1.1870 + command_id); 1.1871 + } 1.1872 + }, 1.1873 + 1.1874 + /** 1.1875 + * Send key presses to element after focusing on it 1.1876 + * 1.1877 + * @param object aRequest 1.1878 + * 'id' member holds the reference id to 1.1879 + * the element that will be checked 1.1880 + * 'value' member holds the value to send to the element 1.1881 + */ 1.1882 + sendKeysToElement: function MDA_sendKeysToElement(aRequest) { 1.1883 + let command_id = this.command_id = this.getCommandId(); 1.1884 + if (this.context == "chrome") { 1.1885 + try { 1.1886 + let el = this.curBrowser.elementManager.getKnownElement( 1.1887 + aRequest.parameters.id, this.getCurrentWindow()); 1.1888 + el.focus(); 1.1889 + utils.sendString(aRequest.parameters.value.join(""), utils.window); 1.1890 + this.sendOk(command_id); 1.1891 + } 1.1892 + catch (e) { 1.1893 + this.sendError(e.message, e.code, e.stack, command_id); 1.1894 + } 1.1895 + } 1.1896 + else { 1.1897 + this.sendAsync("sendKeysToElement", 1.1898 + { 1.1899 + id:aRequest.parameters.id, 1.1900 + value: aRequest.parameters.value 1.1901 + }, 1.1902 + command_id); 1.1903 + } 1.1904 + }, 1.1905 + 1.1906 + /** 1.1907 + * Sets the test name 1.1908 + * 1.1909 + * The test name is used in logging messages. 1.1910 + */ 1.1911 + setTestName: function MDA_setTestName(aRequest) { 1.1912 + this.command_id = this.getCommandId(); 1.1913 + this.logRequest("setTestName", aRequest); 1.1914 + this.testName = aRequest.parameters.value; 1.1915 + this.sendAsync("setTestName", 1.1916 + { value: aRequest.parameters.value }, 1.1917 + this.command_id); 1.1918 + }, 1.1919 + 1.1920 + /** 1.1921 + * Clear the text of an element 1.1922 + * 1.1923 + * @param object aRequest 1.1924 + * 'id' member holds the reference id to 1.1925 + * the element that will be cleared 1.1926 + */ 1.1927 + clearElement: function MDA_clearElement(aRequest) { 1.1928 + let command_id = this.command_id = this.getCommandId(); 1.1929 + if (this.context == "chrome") { 1.1930 + //the selenium atom doesn't work here 1.1931 + try { 1.1932 + let el = this.curBrowser.elementManager.getKnownElement( 1.1933 + aRequest.parameters.id, this.getCurrentWindow()); 1.1934 + if (el.nodeName == "textbox") { 1.1935 + el.value = ""; 1.1936 + } 1.1937 + else if (el.nodeName == "checkbox") { 1.1938 + el.checked = false; 1.1939 + } 1.1940 + this.sendOk(command_id); 1.1941 + } 1.1942 + catch (e) { 1.1943 + this.sendError(e.message, e.code, e.stack, command_id); 1.1944 + } 1.1945 + } 1.1946 + else { 1.1947 + this.sendAsync("clearElement", 1.1948 + { id:aRequest.parameters.id }, 1.1949 + command_id); 1.1950 + } 1.1951 + }, 1.1952 + 1.1953 + /** 1.1954 + * Get an element's location on the page. 1.1955 + * 1.1956 + * The returned point will contain the x and y coordinates of the 1.1957 + * top left-hand corner of the given element. The point (0,0) 1.1958 + * refers to the upper-left corner of the document. 1.1959 + * 1.1960 + * @return a point containing x and y coordinates as properties 1.1961 + */ 1.1962 + getElementLocation: function MDA_getElementLocation(aRequest) { 1.1963 + this.command_id = this.getCommandId(); 1.1964 + this.sendAsync("getElementLocation", {id: aRequest.parameters.id}, 1.1965 + this.command_id); 1.1966 + }, 1.1967 + 1.1968 + /** 1.1969 + * Add a cookie to the document. 1.1970 + */ 1.1971 + addCookie: function MDA_addCookie(aRequest) { 1.1972 + this.command_id = this.getCommandId(); 1.1973 + this.sendAsync("addCookie", 1.1974 + { cookie:aRequest.parameters.cookie }, 1.1975 + this.command_id); 1.1976 + }, 1.1977 + 1.1978 + /** 1.1979 + * Get all the cookies for the current domain. 1.1980 + * 1.1981 + * This is the equivalent of calling "document.cookie" and parsing 1.1982 + * the result. 1.1983 + */ 1.1984 + getCookies: function MDA_getCookies() { 1.1985 + this.command_id = this.getCommandId(); 1.1986 + this.sendAsync("getCookies", {}, this.command_id); 1.1987 + }, 1.1988 + 1.1989 + /** 1.1990 + * Delete all cookies that are visible to a document 1.1991 + */ 1.1992 + deleteAllCookies: function MDA_deleteAllCookies() { 1.1993 + this.command_id = this.getCommandId(); 1.1994 + this.sendAsync("deleteAllCookies", {}, this.command_id); 1.1995 + }, 1.1996 + 1.1997 + /** 1.1998 + * Delete a cookie by name 1.1999 + */ 1.2000 + deleteCookie: function MDA_deleteCookie(aRequest) { 1.2001 + this.command_id = this.getCommandId(); 1.2002 + this.sendAsync("deleteCookie", 1.2003 + { name:aRequest.parameters.name }, 1.2004 + this.command_id); 1.2005 + }, 1.2006 + 1.2007 + /** 1.2008 + * Close the current window, ending the session if it's the last 1.2009 + * window currently open. 1.2010 + * 1.2011 + * On B2G this method is a noop and will return immediately. 1.2012 + */ 1.2013 + close: function MDA_close() { 1.2014 + let command_id = this.command_id = this.getCommandId(); 1.2015 + if (appName == "B2G") { 1.2016 + // We can't close windows so just return 1.2017 + this.sendOk(command_id); 1.2018 + } 1.2019 + else { 1.2020 + // Get the total number of windows 1.2021 + let numOpenWindows = 0; 1.2022 + let winEnum = this.getWinEnumerator(); 1.2023 + while (winEnum.hasMoreElements()) { 1.2024 + numOpenWindows += 1; 1.2025 + winEnum.getNext(); 1.2026 + } 1.2027 + 1.2028 + // if there is only 1 window left, delete the session 1.2029 + if (numOpenWindows === 1) { 1.2030 + try { 1.2031 + this.sessionTearDown(); 1.2032 + } 1.2033 + catch (e) { 1.2034 + this.sendError("Could not clear session", 500, 1.2035 + e.name + ": " + e.message, command_id); 1.2036 + return; 1.2037 + } 1.2038 + this.sendOk(command_id); 1.2039 + return; 1.2040 + } 1.2041 + 1.2042 + try { 1.2043 + this.messageManager.removeDelayedFrameScript(FRAME_SCRIPT); 1.2044 + this.getCurrentWindow().close(); 1.2045 + this.sendOk(command_id); 1.2046 + } 1.2047 + catch (e) { 1.2048 + this.sendError("Could not close window: " + e.message, 13, e.stack, 1.2049 + command_id); 1.2050 + } 1.2051 + } 1.2052 + }, 1.2053 + 1.2054 + /** 1.2055 + * Deletes the session. 1.2056 + * 1.2057 + * If it is a desktop environment, it will close the session's tab and close all listeners 1.2058 + * 1.2059 + * If it is a B2G environment, it will make the main content listener sleep, and close 1.2060 + * all other listeners. The main content listener persists after disconnect (it's the homescreen), 1.2061 + * and can safely be reused. 1.2062 + */ 1.2063 + sessionTearDown: function MDA_sessionTearDown() { 1.2064 + if (this.curBrowser != null) { 1.2065 + if (appName == "B2G") { 1.2066 + this.globalMessageManager.broadcastAsyncMessage( 1.2067 + "Marionette:sleepSession" + this.curBrowser.mainContentId, {}); 1.2068 + this.curBrowser.knownFrames.splice( 1.2069 + this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1); 1.2070 + } 1.2071 + else { 1.2072 + //don't set this pref for B2G since the framescript can be safely reused 1.2073 + Services.prefs.setBoolPref("marionette.contentListener", false); 1.2074 + } 1.2075 + this.curBrowser.closeTab(); 1.2076 + //delete session in each frame in each browser 1.2077 + for (let win in this.browsers) { 1.2078 + for (let i in this.browsers[win].knownFrames) { 1.2079 + this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {}); 1.2080 + } 1.2081 + } 1.2082 + let winEnum = this.getWinEnumerator(); 1.2083 + while (winEnum.hasMoreElements()) { 1.2084 + winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT); 1.2085 + } 1.2086 + this.curBrowser.frameManager.removeMessageManagerListeners(this.globalMessageManager); 1.2087 + } 1.2088 + this.switchToGlobalMessageManager(); 1.2089 + // reset frame to the top-most frame 1.2090 + this.curFrame = null; 1.2091 + if (this.mainFrame) { 1.2092 + this.mainFrame.focus(); 1.2093 + } 1.2094 + this.deleteFile('marionetteChromeScripts'); 1.2095 + this.deleteFile('marionetteContentScripts'); 1.2096 + }, 1.2097 + 1.2098 + /** 1.2099 + * Processes the 'deleteSession' request from the client by tearing down 1.2100 + * the session and responding 'ok'. 1.2101 + */ 1.2102 + deleteSession: function MDA_deleteSession() { 1.2103 + let command_id = this.command_id = this.getCommandId(); 1.2104 + try { 1.2105 + this.sessionTearDown(); 1.2106 + } 1.2107 + catch (e) { 1.2108 + this.sendError("Could not delete session", 500, e.name + ": " + e.message, command_id); 1.2109 + return; 1.2110 + } 1.2111 + this.sendOk(command_id); 1.2112 + }, 1.2113 + 1.2114 + /** 1.2115 + * Returns the current status of the Application Cache 1.2116 + */ 1.2117 + getAppCacheStatus: function MDA_getAppCacheStatus(aRequest) { 1.2118 + this.command_id = this.getCommandId(); 1.2119 + this.sendAsync("getAppCacheStatus", {}, this.command_id); 1.2120 + }, 1.2121 + 1.2122 + _emu_cb_id: 0, 1.2123 + _emu_cbs: null, 1.2124 + runEmulatorCmd: function runEmulatorCmd(cmd, callback) { 1.2125 + if (callback) { 1.2126 + if (!this._emu_cbs) { 1.2127 + this._emu_cbs = {}; 1.2128 + } 1.2129 + this._emu_cbs[this._emu_cb_id] = callback; 1.2130 + } 1.2131 + this.sendToClient({emulator_cmd: cmd, id: this._emu_cb_id}, -1); 1.2132 + this._emu_cb_id += 1; 1.2133 + }, 1.2134 + 1.2135 + runEmulatorShell: function runEmulatorShell(args, callback) { 1.2136 + if (callback) { 1.2137 + if (!this._emu_cbs) { 1.2138 + this._emu_cbs = {}; 1.2139 + } 1.2140 + this._emu_cbs[this._emu_cb_id] = callback; 1.2141 + } 1.2142 + this.sendToClient({emulator_shell: args, id: this._emu_cb_id}, -1); 1.2143 + this._emu_cb_id += 1; 1.2144 + }, 1.2145 + 1.2146 + emulatorCmdResult: function emulatorCmdResult(message) { 1.2147 + if (this.context != "chrome") { 1.2148 + this.sendAsync("emulatorCmdResult", message, -1); 1.2149 + return; 1.2150 + } 1.2151 + 1.2152 + if (!this._emu_cbs) { 1.2153 + return; 1.2154 + } 1.2155 + 1.2156 + let cb = this._emu_cbs[message.id]; 1.2157 + delete this._emu_cbs[message.id]; 1.2158 + if (!cb) { 1.2159 + return; 1.2160 + } 1.2161 + try { 1.2162 + cb(message.result); 1.2163 + } 1.2164 + catch(e) { 1.2165 + this.sendError(e.message, e.code, e.stack, -1); 1.2166 + return; 1.2167 + } 1.2168 + }, 1.2169 + 1.2170 + importScript: function MDA_importScript(aRequest) { 1.2171 + let command_id = this.command_id = this.getCommandId(); 1.2172 + let converter = 1.2173 + Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]. 1.2174 + createInstance(Components.interfaces.nsIScriptableUnicodeConverter); 1.2175 + converter.charset = "UTF-8"; 1.2176 + let result = {}; 1.2177 + let data = converter.convertToByteArray(aRequest.parameters.script, result); 1.2178 + let ch = Components.classes["@mozilla.org/security/hash;1"] 1.2179 + .createInstance(Components.interfaces.nsICryptoHash); 1.2180 + ch.init(ch.MD5); 1.2181 + ch.update(data, data.length); 1.2182 + let hash = ch.finish(true); 1.2183 + if (this.importedScriptHashes[this.context].indexOf(hash) > -1) { 1.2184 + //we have already imported this script 1.2185 + this.sendOk(command_id); 1.2186 + return; 1.2187 + } 1.2188 + this.importedScriptHashes[this.context].push(hash); 1.2189 + if (this.context == "chrome") { 1.2190 + let file; 1.2191 + if (this.importedScripts.exists()) { 1.2192 + file = FileUtils.openFileOutputStream(this.importedScripts, 1.2193 + FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY); 1.2194 + } 1.2195 + else { 1.2196 + //Note: The permission bits here don't actually get set (bug 804563) 1.2197 + this.importedScripts.createUnique( 1.2198 + Components.interfaces.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8)); 1.2199 + file = FileUtils.openFileOutputStream(this.importedScripts, 1.2200 + FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE); 1.2201 + this.importedScripts.permissions = parseInt("0666", 8); //actually set permissions 1.2202 + } 1.2203 + file.write(aRequest.parameters.script, aRequest.parameters.script.length); 1.2204 + file.close(); 1.2205 + this.sendOk(command_id); 1.2206 + } 1.2207 + else { 1.2208 + this.sendAsync("importScript", 1.2209 + { script: aRequest.parameters.script }, 1.2210 + command_id); 1.2211 + } 1.2212 + }, 1.2213 + 1.2214 + clearImportedScripts: function MDA_clearImportedScripts(aRequest) { 1.2215 + let command_id = this.command_id = this.getCommandId(); 1.2216 + try { 1.2217 + if (this.context == "chrome") { 1.2218 + this.deleteFile('marionetteChromeScripts'); 1.2219 + } 1.2220 + else { 1.2221 + this.deleteFile('marionetteContentScripts'); 1.2222 + } 1.2223 + } 1.2224 + catch (e) { 1.2225 + this.sendError("Could not clear imported scripts", 500, e.name + ": " + e.message, command_id); 1.2226 + return; 1.2227 + } 1.2228 + this.sendOk(command_id); 1.2229 + }, 1.2230 + 1.2231 + /** 1.2232 + * Takes a screenshot of a web element or the current frame. 1.2233 + * 1.2234 + * The screen capture is returned as a lossless PNG image encoded as 1.2235 + * a base 64 string. If the <code>id</code> argument is not null 1.2236 + * and refers to a present and visible web element's ID, the capture 1.2237 + * area will be limited to the bounding box of that element. 1.2238 + * Otherwise, the capture area will be the bounding box of the 1.2239 + * current frame. 1.2240 + * 1.2241 + * @param id an optional reference to a web element 1.2242 + * @param highlights an optional list of web elements to draw a red 1.2243 + * box around in the returned capture 1.2244 + * @return PNG image encoded as base 64 string 1.2245 + */ 1.2246 + takeScreenshot: function MDA_takeScreenshot(aRequest) { 1.2247 + this.command_id = this.getCommandId(); 1.2248 + this.sendAsync("takeScreenshot", 1.2249 + {id: aRequest.parameters.id, 1.2250 + highlights: aRequest.parameters.highlights}, 1.2251 + this.command_id); 1.2252 + }, 1.2253 + 1.2254 + /** 1.2255 + * Get the current browser orientation. 1.2256 + * 1.2257 + * Will return one of the valid primary orientation values 1.2258 + * portrait-primary, landscape-primary, portrait-secondary, or 1.2259 + * landscape-secondary. 1.2260 + */ 1.2261 + getScreenOrientation: function MDA_getScreenOrientation(aRequest) { 1.2262 + this.command_id = this.getCommandId(); 1.2263 + let curWindow = this.getCurrentWindow(); 1.2264 + let or = curWindow.screen.mozOrientation; 1.2265 + this.sendResponse(or, this.command_id); 1.2266 + }, 1.2267 + 1.2268 + /** 1.2269 + * Set the current browser orientation. 1.2270 + * 1.2271 + * The supplied orientation should be given as one of the valid 1.2272 + * orientation values. If the orientation is unknown, an error will 1.2273 + * be raised. 1.2274 + * 1.2275 + * Valid orientations are "portrait" and "landscape", which fall 1.2276 + * back to "portrait-primary" and "landscape-primary" respectively, 1.2277 + * and "portrait-secondary" as well as "landscape-secondary". 1.2278 + */ 1.2279 + setScreenOrientation: function MDA_setScreenOrientation(aRequest) { 1.2280 + const ors = ["portrait", "landscape", 1.2281 + "portrait-primary", "landscape-primary", 1.2282 + "portrait-secondary", "landscape-secondary"]; 1.2283 + 1.2284 + this.command_id = this.getCommandId(); 1.2285 + let or = String(aRequest.parameters.orientation); 1.2286 + 1.2287 + let mozOr = or.toLowerCase(); 1.2288 + if (ors.indexOf(mozOr) < 0) { 1.2289 + this.sendError("Unknown screen orientation: " + or, 500, null, 1.2290 + this.command_id); 1.2291 + return; 1.2292 + } 1.2293 + 1.2294 + let curWindow = this.getCurrentWindow(); 1.2295 + if (!curWindow.screen.mozLockOrientation(mozOr)) { 1.2296 + this.sendError("Unable to set screen orientation: " + or, 500, 1.2297 + null, this.command_id); 1.2298 + } 1.2299 + this.sendOk(this.command_id); 1.2300 + }, 1.2301 + 1.2302 + /** 1.2303 + * Helper function to convert an outerWindowID into a UID that Marionette 1.2304 + * tracks. 1.2305 + */ 1.2306 + generateFrameId: function MDA_generateFrameId(id) { 1.2307 + let uid = id + (appName == "B2G" ? "-b2g" : ""); 1.2308 + return uid; 1.2309 + }, 1.2310 + 1.2311 + /** 1.2312 + * Receives all messages from content messageManager 1.2313 + */ 1.2314 + receiveMessage: function MDA_receiveMessage(message) { 1.2315 + // We need to just check if we need to remove the mozbrowserclose listener 1.2316 + if (this.mozBrowserClose !== null){ 1.2317 + let curWindow = this.getCurrentWindow(); 1.2318 + curWindow.removeEventListener('mozbrowserclose', this.mozBrowserClose, true); 1.2319 + this.mozBrowserClose = null; 1.2320 + } 1.2321 + 1.2322 + switch (message.name) { 1.2323 + case "Marionette:done": 1.2324 + this.sendResponse(message.json.value, message.json.command_id); 1.2325 + break; 1.2326 + case "Marionette:ok": 1.2327 + this.sendOk(message.json.command_id); 1.2328 + break; 1.2329 + case "Marionette:error": 1.2330 + this.sendError(message.json.message, message.json.status, message.json.stacktrace, message.json.command_id); 1.2331 + break; 1.2332 + case "Marionette:log": 1.2333 + //log server-side messages 1.2334 + logger.info(message.json.message); 1.2335 + break; 1.2336 + case "Marionette:shareData": 1.2337 + //log messages from tests 1.2338 + if (message.json.log) { 1.2339 + this.marionetteLog.addLogs(message.json.log); 1.2340 + } 1.2341 + break; 1.2342 + case "Marionette:runEmulatorCmd": 1.2343 + case "Marionette:runEmulatorShell": 1.2344 + this.sendToClient(message.json, -1); 1.2345 + break; 1.2346 + case "Marionette:switchToFrame": 1.2347 + this.curBrowser.frameManager.switchToFrame(message); 1.2348 + this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get(); 1.2349 + break; 1.2350 + case "Marionette:switchToModalOrigin": 1.2351 + this.curBrowser.frameManager.switchToModalOrigin(message); 1.2352 + this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get(); 1.2353 + break; 1.2354 + case "Marionette:switchedToFrame": 1.2355 + logger.info("Switched to frame: " + JSON.stringify(message.json)); 1.2356 + if (message.json.restorePrevious) { 1.2357 + this.currentFrameElement = this.previousFrameElement; 1.2358 + } 1.2359 + else { 1.2360 + if (message.json.storePrevious) { 1.2361 + // we don't arbitrarily save previousFrameElement, since 1.2362 + // we allow frame switching after modals appear, which would 1.2363 + // override this value and we'd lose our reference 1.2364 + this.previousFrameElement = this.currentFrameElement; 1.2365 + } 1.2366 + this.currentFrameElement = message.json.frameValue; 1.2367 + } 1.2368 + break; 1.2369 + case "Marionette:register": 1.2370 + // This code processes the content listener's registration information 1.2371 + // and either accepts the listener, or ignores it 1.2372 + let nullPrevious = (this.curBrowser.curFrameId == null); 1.2373 + let listenerWindow = 1.2374 + Services.wm.getOuterWindowWithId(message.json.value); 1.2375 + 1.2376 + //go in here if we're already in a remote frame. 1.2377 + if ((!listenerWindow || (listenerWindow.location && 1.2378 + listenerWindow.location.href != message.json.href)) && 1.2379 + (this.curBrowser.frameManager.currentRemoteFrame !== null)) { 1.2380 + // The outerWindowID from an OOP frame will not be meaningful to 1.2381 + // the parent process here, since each process maintains its own 1.2382 + // independent window list. So, it will either be null (!listenerWindow) 1.2383 + // if we're already in a remote frame, 1.2384 + // or it will point to some random window, which will hopefully 1.2385 + // cause an href mismatch. Currently this only happens 1.2386 + // in B2G for OOP frames registered in Marionette:switchToFrame, so 1.2387 + // we'll acknowledge the switchToFrame message here. 1.2388 + // XXX: Should have a better way of determining that this message 1.2389 + // is from a remote frame. 1.2390 + this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value); 1.2391 + this.sendOk(this.command_id); 1.2392 + } 1.2393 + 1.2394 + let browserType; 1.2395 + try { 1.2396 + browserType = message.target.getAttribute("type"); 1.2397 + } catch (ex) { 1.2398 + // browserType remains undefined. 1.2399 + } 1.2400 + let reg = {}; 1.2401 + // this will be sent to tell the content process if it is the main content 1.2402 + let mainContent = (this.curBrowser.mainContentId == null); 1.2403 + if (!browserType || browserType != "content") { 1.2404 + //curBrowser holds all the registered frames in knownFrames 1.2405 + reg.id = this.curBrowser.register(this.generateFrameId(message.json.value), 1.2406 + listenerWindow); 1.2407 + } 1.2408 + // set to true if we updated mainContentId 1.2409 + mainContent = ((mainContent == true) && (this.curBrowser.mainContentId != null)); 1.2410 + if (mainContent) { 1.2411 + this.mainContentFrameId = this.curBrowser.curFrameId; 1.2412 + } 1.2413 + this.curBrowser.elementManager.seenItems[reg.id] = Cu.getWeakReference(listenerWindow); 1.2414 + if (nullPrevious && (this.curBrowser.curFrameId != null)) { 1.2415 + if (!this.sendAsync("newSession", 1.2416 + { B2G: (appName == "B2G") }, 1.2417 + this.newSessionCommandId)) { 1.2418 + return; 1.2419 + } 1.2420 + if (this.curBrowser.newSession) { 1.2421 + this.getSessionCapabilities(); 1.2422 + this.newSessionCommandId = null; 1.2423 + } 1.2424 + } 1.2425 + return [reg, mainContent]; 1.2426 + case "Marionette:emitTouchEvent": 1.2427 + let globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"] 1.2428 + .getService(Ci.nsIMessageBroadcaster); 1.2429 + globalMessageManager.broadcastAsyncMessage( 1.2430 + "MarionetteMainListener:emitTouchEvent", message.json); 1.2431 + return; 1.2432 + } 1.2433 + } 1.2434 +}; 1.2435 + 1.2436 +MarionetteServerConnection.prototype.requestTypes = { 1.2437 + "getMarionetteID": MarionetteServerConnection.prototype.getMarionetteID, 1.2438 + "sayHello": MarionetteServerConnection.prototype.sayHello, 1.2439 + "newSession": MarionetteServerConnection.prototype.newSession, 1.2440 + "getSessionCapabilities": MarionetteServerConnection.prototype.getSessionCapabilities, 1.2441 + "log": MarionetteServerConnection.prototype.log, 1.2442 + "getLogs": MarionetteServerConnection.prototype.getLogs, 1.2443 + "setContext": MarionetteServerConnection.prototype.setContext, 1.2444 + "executeScript": MarionetteServerConnection.prototype.execute, 1.2445 + "setScriptTimeout": MarionetteServerConnection.prototype.setScriptTimeout, 1.2446 + "timeouts": MarionetteServerConnection.prototype.timeouts, 1.2447 + "singleTap": MarionetteServerConnection.prototype.singleTap, 1.2448 + "actionChain": MarionetteServerConnection.prototype.actionChain, 1.2449 + "multiAction": MarionetteServerConnection.prototype.multiAction, 1.2450 + "executeAsyncScript": MarionetteServerConnection.prototype.executeWithCallback, 1.2451 + "executeJSScript": MarionetteServerConnection.prototype.executeJSScript, 1.2452 + "setSearchTimeout": MarionetteServerConnection.prototype.setSearchTimeout, 1.2453 + "findElement": MarionetteServerConnection.prototype.findElement, 1.2454 + "findElements": MarionetteServerConnection.prototype.findElements, 1.2455 + "clickElement": MarionetteServerConnection.prototype.clickElement, 1.2456 + "getElementAttribute": MarionetteServerConnection.prototype.getElementAttribute, 1.2457 + "getElementText": MarionetteServerConnection.prototype.getElementText, 1.2458 + "getElementTagName": MarionetteServerConnection.prototype.getElementTagName, 1.2459 + "isElementDisplayed": MarionetteServerConnection.prototype.isElementDisplayed, 1.2460 + "getElementValueOfCssProperty": MarionetteServerConnection.prototype.getElementValueOfCssProperty, 1.2461 + "submitElement": MarionetteServerConnection.prototype.submitElement, 1.2462 + "getElementSize": MarionetteServerConnection.prototype.getElementSize, 1.2463 + "isElementEnabled": MarionetteServerConnection.prototype.isElementEnabled, 1.2464 + "isElementSelected": MarionetteServerConnection.prototype.isElementSelected, 1.2465 + "sendKeysToElement": MarionetteServerConnection.prototype.sendKeysToElement, 1.2466 + "getElementLocation": MarionetteServerConnection.prototype.getElementLocation, 1.2467 + "getElementPosition": MarionetteServerConnection.prototype.getElementLocation, // deprecated 1.2468 + "clearElement": MarionetteServerConnection.prototype.clearElement, 1.2469 + "getTitle": MarionetteServerConnection.prototype.getTitle, 1.2470 + "getWindowType": MarionetteServerConnection.prototype.getWindowType, 1.2471 + "getPageSource": MarionetteServerConnection.prototype.getPageSource, 1.2472 + "get": MarionetteServerConnection.prototype.get, 1.2473 + "goUrl": MarionetteServerConnection.prototype.get, // deprecated 1.2474 + "getCurrentUrl": MarionetteServerConnection.prototype.getCurrentUrl, 1.2475 + "getUrl": MarionetteServerConnection.prototype.getCurrentUrl, // deprecated 1.2476 + "goBack": MarionetteServerConnection.prototype.goBack, 1.2477 + "goForward": MarionetteServerConnection.prototype.goForward, 1.2478 + "refresh": MarionetteServerConnection.prototype.refresh, 1.2479 + "getWindowHandle": MarionetteServerConnection.prototype.getWindowHandle, 1.2480 + "getCurrentWindowHandle": MarionetteServerConnection.prototype.getWindowHandle, // Selenium 2 compat 1.2481 + "getWindow": MarionetteServerConnection.prototype.getWindowHandle, // deprecated 1.2482 + "getWindowHandles": MarionetteServerConnection.prototype.getWindowHandles, 1.2483 + "getCurrentWindowHandles": MarionetteServerConnection.prototype.getWindowHandles, // Selenium 2 compat 1.2484 + "getWindows": MarionetteServerConnection.prototype.getWindowHandles, // deprecated 1.2485 + "getActiveFrame": MarionetteServerConnection.prototype.getActiveFrame, 1.2486 + "switchToFrame": MarionetteServerConnection.prototype.switchToFrame, 1.2487 + "switchToWindow": MarionetteServerConnection.prototype.switchToWindow, 1.2488 + "deleteSession": MarionetteServerConnection.prototype.deleteSession, 1.2489 + "emulatorCmdResult": MarionetteServerConnection.prototype.emulatorCmdResult, 1.2490 + "importScript": MarionetteServerConnection.prototype.importScript, 1.2491 + "clearImportedScripts": MarionetteServerConnection.prototype.clearImportedScripts, 1.2492 + "getAppCacheStatus": MarionetteServerConnection.prototype.getAppCacheStatus, 1.2493 + "close": MarionetteServerConnection.prototype.close, 1.2494 + "closeWindow": MarionetteServerConnection.prototype.close, // deprecated 1.2495 + "setTestName": MarionetteServerConnection.prototype.setTestName, 1.2496 + "takeScreenshot": MarionetteServerConnection.prototype.takeScreenshot, 1.2497 + "screenShot": MarionetteServerConnection.prototype.takeScreenshot, // deprecated 1.2498 + "screenshot": MarionetteServerConnection.prototype.takeScreenshot, // Selenium 2 compat 1.2499 + "addCookie": MarionetteServerConnection.prototype.addCookie, 1.2500 + "getCookies": MarionetteServerConnection.prototype.getCookies, 1.2501 + "getAllCookies": MarionetteServerConnection.prototype.getCookies, // deprecated 1.2502 + "deleteAllCookies": MarionetteServerConnection.prototype.deleteAllCookies, 1.2503 + "deleteCookie": MarionetteServerConnection.prototype.deleteCookie, 1.2504 + "getActiveElement": MarionetteServerConnection.prototype.getActiveElement, 1.2505 + "getScreenOrientation": MarionetteServerConnection.prototype.getScreenOrientation, 1.2506 + "setScreenOrientation": MarionetteServerConnection.prototype.setScreenOrientation 1.2507 +}; 1.2508 + 1.2509 +/** 1.2510 + * Creates a BrowserObj. BrowserObjs handle interactions with the 1.2511 + * browser, according to the current environment (desktop, b2g, etc.) 1.2512 + * 1.2513 + * @param nsIDOMWindow win 1.2514 + * The window whose browser needs to be accessed 1.2515 + */ 1.2516 + 1.2517 +function BrowserObj(win, server) { 1.2518 + this.DESKTOP = "desktop"; 1.2519 + this.B2G = "B2G"; 1.2520 + this.browser; 1.2521 + this.tab = null; //Holds a reference to the created tab, if any 1.2522 + this.window = win; 1.2523 + this.knownFrames = []; 1.2524 + this.curFrameId = null; 1.2525 + this.startPage = "about:blank"; 1.2526 + this.mainContentId = null; // used in B2G to identify the homescreen content page 1.2527 + this.newSession = true; //used to set curFrameId upon new session 1.2528 + this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]); 1.2529 + this.setBrowser(win); 1.2530 + this.frameManager = new FrameManager(server); //We should have one FM per BO so that we can handle modals in each Browser 1.2531 + 1.2532 + //register all message listeners 1.2533 + this.frameManager.addMessageManagerListeners(server.messageManager); 1.2534 +} 1.2535 + 1.2536 +BrowserObj.prototype = { 1.2537 + /** 1.2538 + * Set the browser if the application is not B2G 1.2539 + * 1.2540 + * @param nsIDOMWindow win 1.2541 + * current window reference 1.2542 + */ 1.2543 + setBrowser: function BO_setBrowser(win) { 1.2544 + switch (appName) { 1.2545 + case "Firefox": 1.2546 + this.browser = win.gBrowser; 1.2547 + break; 1.2548 + case "Fennec": 1.2549 + this.browser = win.BrowserApp; 1.2550 + break; 1.2551 + } 1.2552 + }, 1.2553 + /** 1.2554 + * Called when we start a session with this browser. 1.2555 + * 1.2556 + * In a desktop environment, if newTab is true, it will start 1.2557 + * a new 'about:blank' tab and change focus to this tab. 1.2558 + * 1.2559 + * This will also set the active messagemanager for this object 1.2560 + * 1.2561 + * @param boolean newTab 1.2562 + * If true, create new tab 1.2563 + */ 1.2564 + startSession: function BO_startSession(newTab, win, callback) { 1.2565 + if (appName != "Firefox") { 1.2566 + callback(win, newTab); 1.2567 + } 1.2568 + else if (newTab) { 1.2569 + this.tab = this.addTab(this.startPage); 1.2570 + //if we have a new tab, make it the selected tab 1.2571 + this.browser.selectedTab = this.tab; 1.2572 + let newTabBrowser = this.browser.getBrowserForTab(this.tab); 1.2573 + // wait for tab to be loaded 1.2574 + newTabBrowser.addEventListener("load", function onLoad() { 1.2575 + newTabBrowser.removeEventListener("load", onLoad, true); 1.2576 + callback(win, newTab); 1.2577 + }, true); 1.2578 + } 1.2579 + else { 1.2580 + //set this.tab to the currently focused tab 1.2581 + if (this.browser != undefined && this.browser.selectedTab != undefined) { 1.2582 + this.tab = this.browser.selectedTab; 1.2583 + } 1.2584 + callback(win, newTab); 1.2585 + } 1.2586 + }, 1.2587 + 1.2588 + /** 1.2589 + * Closes current tab 1.2590 + */ 1.2591 + closeTab: function BO_closeTab() { 1.2592 + if (this.tab != null && (appName != "B2G")) { 1.2593 + this.browser.removeTab(this.tab); 1.2594 + this.tab = null; 1.2595 + } 1.2596 + }, 1.2597 + 1.2598 + /** 1.2599 + * Opens a tab with given uri 1.2600 + * 1.2601 + * @param string uri 1.2602 + * URI to open 1.2603 + */ 1.2604 + addTab: function BO_addTab(uri) { 1.2605 + return this.browser.addTab(uri, true); 1.2606 + }, 1.2607 + 1.2608 + /** 1.2609 + * Loads content listeners if we don't already have them 1.2610 + * 1.2611 + * @param string script 1.2612 + * path of script to load 1.2613 + * @param nsIDOMWindow frame 1.2614 + * frame to load the script in 1.2615 + */ 1.2616 + loadFrameScript: function BO_loadFrameScript(script, frame) { 1.2617 + frame.window.messageManager.loadFrameScript(script, true, true); 1.2618 + Services.prefs.setBoolPref("marionette.contentListener", true); 1.2619 + }, 1.2620 + 1.2621 + /** 1.2622 + * Registers a new frame, and sets its current frame id to this frame 1.2623 + * if it is not already assigned, and if a) we already have a session 1.2624 + * or b) we're starting a new session and it is the right start frame. 1.2625 + * 1.2626 + * @param string uid 1.2627 + * frame uid 1.2628 + * @param object frameWindow 1.2629 + * the DOMWindow object of the frame that's being registered 1.2630 + */ 1.2631 + register: function BO_register(uid, frameWindow) { 1.2632 + if (this.curFrameId == null) { 1.2633 + // If we're setting up a new session on Firefox, we only process the 1.2634 + // registration for this frame if it belongs to the tab we've just 1.2635 + // created. 1.2636 + if ((!this.newSession) || 1.2637 + (this.newSession && 1.2638 + ((appName != "Firefox") || 1.2639 + frameWindow == this.browser.getBrowserForTab(this.tab).contentWindow))) { 1.2640 + this.curFrameId = uid; 1.2641 + this.mainContentId = uid; 1.2642 + } 1.2643 + } 1.2644 + this.knownFrames.push(uid); //used to delete sessions 1.2645 + return uid; 1.2646 + }, 1.2647 +} 1.2648 + 1.2649 +/** 1.2650 + * Marionette server -- this class holds a reference to a socket and creates 1.2651 + * MarionetteServerConnection objects as needed. 1.2652 + */ 1.2653 +this.MarionetteServer = function MarionetteServer(port, forceLocal) { 1.2654 + let flags = Ci.nsIServerSocket.KeepWhenOffline; 1.2655 + if (forceLocal) { 1.2656 + flags |= Ci.nsIServerSocket.LoopbackOnly; 1.2657 + } 1.2658 + let socket = new ServerSocket(port, flags, 0); 1.2659 + logger.info("Listening on port " + socket.port + "\n"); 1.2660 + socket.asyncListen(this); 1.2661 + this.listener = socket; 1.2662 + this.nextConnId = 0; 1.2663 + this.connections = {}; 1.2664 +}; 1.2665 + 1.2666 +MarionetteServer.prototype = { 1.2667 + onSocketAccepted: function(serverSocket, clientSocket) 1.2668 + { 1.2669 + logger.debug("accepted connection on " + clientSocket.host + ":" + clientSocket.port); 1.2670 + 1.2671 + let input = clientSocket.openInputStream(0, 0, 0); 1.2672 + let output = clientSocket.openOutputStream(0, 0, 0); 1.2673 + let aTransport = new DebuggerTransport(input, output); 1.2674 + let connID = "conn" + this.nextConnID++ + '.'; 1.2675 + let conn = new MarionetteServerConnection(connID, aTransport, this); 1.2676 + this.connections[connID] = conn; 1.2677 + 1.2678 + // Create a root actor for the connection and send the hello packet. 1.2679 + conn.sayHello(); 1.2680 + aTransport.ready(); 1.2681 + }, 1.2682 + 1.2683 + closeListener: function() { 1.2684 + this.listener.close(); 1.2685 + this.listener = null; 1.2686 + }, 1.2687 + 1.2688 + _connectionClosed: function DS_connectionClosed(aConnection) { 1.2689 + delete this.connections[aConnection.prefix]; 1.2690 + } 1.2691 +};