testing/marionette/marionette-server.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
michael@0 8
michael@0 9 // import logger
michael@0 10 Cu.import("resource://gre/modules/Log.jsm");
michael@0 11 let logger = Log.repository.getLogger("Marionette");
michael@0 12 logger.info('marionette-server.js loaded');
michael@0 13
michael@0 14 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
michael@0 15 .getService(Ci.mozIJSSubScriptLoader);
michael@0 16 loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
michael@0 17 loader.loadSubScript("chrome://marionette/content/marionette-common.js");
michael@0 18 Cu.import("resource://gre/modules/Services.jsm");
michael@0 19 loader.loadSubScript("chrome://marionette/content/marionette-frame-manager.js");
michael@0 20 Cu.import("chrome://marionette/content/marionette-elements.js");
michael@0 21 let utils = {};
michael@0 22 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
michael@0 23 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
michael@0 24 loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
michael@0 25
michael@0 26 let specialpowers = {};
michael@0 27 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
michael@0 28 specialpowers);
michael@0 29 specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver();
michael@0 30 specialpowers.specialPowersObserver.init();
michael@0 31
michael@0 32 Cu.import("resource://gre/modules/FileUtils.jsm");
michael@0 33 Cu.import("resource://gre/modules/NetUtil.jsm");
michael@0 34
michael@0 35 Services.prefs.setBoolPref("marionette.contentListener", false);
michael@0 36 let appName = Services.appinfo.name;
michael@0 37
michael@0 38 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
michael@0 39 let DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
michael@0 40 this.DevToolsUtils = DevToolsUtils;
michael@0 41 loader.loadSubScript("resource://gre/modules/devtools/server/transport.js");
michael@0 42
michael@0 43 let bypassOffline = false;
michael@0 44 let qemu = "0";
michael@0 45 let device = null;
michael@0 46
michael@0 47 try {
michael@0 48 XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
michael@0 49 Cu.import("resource://gre/modules/systemlibs.js");
michael@0 50 return libcutils;
michael@0 51 });
michael@0 52 if (libcutils) {
michael@0 53 qemu = libcutils.property_get("ro.kernel.qemu");
michael@0 54 logger.info("B2G emulator: " + (qemu == "1" ? "yes" : "no"));
michael@0 55 device = libcutils.property_get("ro.product.device");
michael@0 56 logger.info("Device detected is " + device);
michael@0 57 bypassOffline = (qemu == "1" || device == "panda");
michael@0 58 }
michael@0 59 }
michael@0 60 catch(e) {}
michael@0 61
michael@0 62 if (bypassOffline) {
michael@0 63 logger.info("Bypassing offline status.");
michael@0 64 Services.prefs.setBoolPref("network.gonk.manage-offline-status", false);
michael@0 65 Services.io.manageOfflineStatus = false;
michael@0 66 Services.io.offline = false;
michael@0 67 }
michael@0 68
michael@0 69 // This is used to prevent newSession from returning before the telephony
michael@0 70 // API's are ready; see bug 792647. This assumes that marionette-server.js
michael@0 71 // will be loaded before the 'system-message-listener-ready' message
michael@0 72 // is fired. If this stops being true, this approach will have to change.
michael@0 73 let systemMessageListenerReady = false;
michael@0 74 Services.obs.addObserver(function() {
michael@0 75 systemMessageListenerReady = true;
michael@0 76 }, "system-message-listener-ready", false);
michael@0 77
michael@0 78 /*
michael@0 79 * Custom exceptions
michael@0 80 */
michael@0 81 function FrameSendNotInitializedError(frame) {
michael@0 82 this.code = 54;
michael@0 83 this.frame = frame;
michael@0 84 this.message = "Error sending message to frame (NS_ERROR_NOT_INITIALIZED)";
michael@0 85 this.toString = function() {
michael@0 86 return this.message + " " + this.frame + "; frame has closed.";
michael@0 87 }
michael@0 88 }
michael@0 89
michael@0 90 function FrameSendFailureError(frame) {
michael@0 91 this.code = 55;
michael@0 92 this.frame = frame;
michael@0 93 this.message = "Error sending message to frame (NS_ERROR_FAILURE)";
michael@0 94 this.toString = function() {
michael@0 95 return this.message + " " + this.frame + "; frame not responding.";
michael@0 96 }
michael@0 97 }
michael@0 98
michael@0 99 /**
michael@0 100 * The server connection is responsible for all marionette API calls. It gets created
michael@0 101 * for each connection and manages all chrome and browser based calls. It
michael@0 102 * mediates content calls by issuing appropriate messages to the content process.
michael@0 103 */
michael@0 104 function MarionetteServerConnection(aPrefix, aTransport, aServer)
michael@0 105 {
michael@0 106 this.uuidGen = Cc["@mozilla.org/uuid-generator;1"]
michael@0 107 .getService(Ci.nsIUUIDGenerator);
michael@0 108
michael@0 109 this.prefix = aPrefix;
michael@0 110 this.server = aServer;
michael@0 111 this.conn = aTransport;
michael@0 112 this.conn.hooks = this;
michael@0 113
michael@0 114 // marionette uses a protocol based on the debugger server, which requires
michael@0 115 // passing back "actor ids" with responses. unlike the debugger server,
michael@0 116 // we don't have multiple actors, so just use a dummy value of "0" here
michael@0 117 this.actorID = "0";
michael@0 118
michael@0 119 this.globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
michael@0 120 .getService(Ci.nsIMessageBroadcaster);
michael@0 121 this.messageManager = this.globalMessageManager;
michael@0 122 this.browsers = {}; //holds list of BrowserObjs
michael@0 123 this.curBrowser = null; // points to current browser
michael@0 124 this.context = "content";
michael@0 125 this.scriptTimeout = null;
michael@0 126 this.searchTimeout = null;
michael@0 127 this.pageTimeout = null;
michael@0 128 this.timer = null;
michael@0 129 this.inactivityTimer = null;
michael@0 130 this.heartbeatCallback = function () {}; // called by simpletest methods
michael@0 131 this.marionetteLog = new MarionetteLogObj();
michael@0 132 this.command_id = null;
michael@0 133 this.mainFrame = null; //topmost chrome frame
michael@0 134 this.curFrame = null; // chrome iframe that currently has focus
michael@0 135 this.mainContentFrameId = null;
michael@0 136 this.importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']);
michael@0 137 this.importedScriptHashes = {"chrome" : [], "content": []};
michael@0 138 this.currentFrameElement = null;
michael@0 139 this.testName = null;
michael@0 140 this.mozBrowserClose = null;
michael@0 141 }
michael@0 142
michael@0 143 MarionetteServerConnection.prototype = {
michael@0 144
michael@0 145 QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
michael@0 146 Ci.nsIObserver,
michael@0 147 Ci.nsISupportsWeakReference]),
michael@0 148
michael@0 149 /**
michael@0 150 * Debugger transport callbacks:
michael@0 151 */
michael@0 152 onPacket: function MSC_onPacket(aPacket) {
michael@0 153 // Dispatch the request
michael@0 154 if (this.requestTypes && this.requestTypes[aPacket.name]) {
michael@0 155 try {
michael@0 156 this.requestTypes[aPacket.name].bind(this)(aPacket);
michael@0 157 } catch(e) {
michael@0 158 this.conn.send({ error: ("error occurred while processing '" +
michael@0 159 aPacket.name),
michael@0 160 message: e.message });
michael@0 161 }
michael@0 162 } else {
michael@0 163 this.conn.send({ error: "unrecognizedPacketType",
michael@0 164 message: ('Marionette does not ' +
michael@0 165 'recognize the packet type "' +
michael@0 166 aPacket.name + '"') });
michael@0 167 }
michael@0 168 },
michael@0 169
michael@0 170 onClosed: function MSC_onClosed(aStatus) {
michael@0 171 this.server._connectionClosed(this);
michael@0 172 this.sessionTearDown();
michael@0 173 },
michael@0 174
michael@0 175 /**
michael@0 176 * Helper methods:
michael@0 177 */
michael@0 178
michael@0 179 /**
michael@0 180 * Switches to the global ChromeMessageBroadcaster, potentially replacing a frame-specific
michael@0 181 * ChromeMessageSender. Has no effect if the global ChromeMessageBroadcaster is already
michael@0 182 * in use. If this replaces a frame-specific ChromeMessageSender, it removes the message
michael@0 183 * listeners from that sender, and then puts the corresponding frame script "to sleep",
michael@0 184 * which removes most of the message listeners from it as well.
michael@0 185 */
michael@0 186 switchToGlobalMessageManager: function MDA_switchToGlobalMM() {
michael@0 187 if (this.curBrowser && this.curBrowser.frameManager.currentRemoteFrame !== null) {
michael@0 188 this.curBrowser.frameManager.removeMessageManagerListeners(this.messageManager);
michael@0 189 this.sendAsync("sleepSession", null, null, true);
michael@0 190 this.curBrowser.frameManager.currentRemoteFrame = null;
michael@0 191 }
michael@0 192 this.messageManager = this.globalMessageManager;
michael@0 193 },
michael@0 194
michael@0 195 /**
michael@0 196 * Helper method to send async messages to the content listener
michael@0 197 *
michael@0 198 * @param string name
michael@0 199 * Suffix of the targetted message listener (Marionette:<suffix>)
michael@0 200 * @param object values
michael@0 201 * Object to send to the listener
michael@0 202 */
michael@0 203 sendAsync: function MDA_sendAsync(name, values, commandId, ignoreFailure) {
michael@0 204 let success = true;
michael@0 205 if (values instanceof Object && commandId) {
michael@0 206 values.command_id = commandId;
michael@0 207 }
michael@0 208 if (this.curBrowser.frameManager.currentRemoteFrame !== null) {
michael@0 209 try {
michael@0 210 this.messageManager.sendAsyncMessage(
michael@0 211 "Marionette:" + name + this.curBrowser.frameManager.currentRemoteFrame.targetFrameId, values);
michael@0 212 }
michael@0 213 catch(e) {
michael@0 214 if (!ignoreFailure) {
michael@0 215 success = false;
michael@0 216 let error = e;
michael@0 217 switch(e.result) {
michael@0 218 case Components.results.NS_ERROR_FAILURE:
michael@0 219 error = new FrameSendFailureError(this.curBrowser.frameManager.currentRemoteFrame);
michael@0 220 break;
michael@0 221 case Components.results.NS_ERROR_NOT_INITIALIZED:
michael@0 222 error = new FrameSendNotInitializedError(this.curBrowser.frameManager.currentRemoteFrame);
michael@0 223 break;
michael@0 224 default:
michael@0 225 break;
michael@0 226 }
michael@0 227 let code = error.hasOwnProperty('code') ? e.code : 500;
michael@0 228 this.sendError(error.toString(), code, error.stack, commandId);
michael@0 229 }
michael@0 230 }
michael@0 231 }
michael@0 232 else {
michael@0 233 this.messageManager.broadcastAsyncMessage(
michael@0 234 "Marionette:" + name + this.curBrowser.curFrameId, values);
michael@0 235 }
michael@0 236 return success;
michael@0 237 },
michael@0 238
michael@0 239 logRequest: function MDA_logRequest(type, data) {
michael@0 240 logger.debug("Got request: " + type + ", data: " + JSON.stringify(data) + ", id: " + this.command_id);
michael@0 241 },
michael@0 242
michael@0 243 /**
michael@0 244 * Generic method to pass a response to the client
michael@0 245 *
michael@0 246 * @param object msg
michael@0 247 * Response to send back to client
michael@0 248 * @param string command_id
michael@0 249 * Unique identifier assigned to the client's request.
michael@0 250 * Used to distinguish the asynchronous responses.
michael@0 251 */
michael@0 252 sendToClient: function MDA_sendToClient(msg, command_id) {
michael@0 253 logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id +
michael@0 254 ", " + this.command_id);
michael@0 255 if (!command_id) {
michael@0 256 logger.warn("got a response with no command_id");
michael@0 257 return;
michael@0 258 }
michael@0 259 else if (command_id != -1) {
michael@0 260 // A command_id of -1 is used for emulator callbacks, and those
michael@0 261 // don't use this.command_id.
michael@0 262 if (!this.command_id) {
michael@0 263 // A null value for this.command_id means we've already processed
michael@0 264 // a message for the previous value, and so the current message is a
michael@0 265 // duplicate.
michael@0 266 logger.warn("ignoring duplicate response for command_id " + command_id);
michael@0 267 return;
michael@0 268 }
michael@0 269 else if (this.command_id != command_id) {
michael@0 270 logger.warn("ignoring out-of-sync response");
michael@0 271 return;
michael@0 272 }
michael@0 273 }
michael@0 274 this.conn.send(msg);
michael@0 275 if (command_id != -1) {
michael@0 276 // Don't unset this.command_id if this message is to process an
michael@0 277 // emulator callback, since another response for this command_id is
michael@0 278 // expected, after the containing call to execute_async_script finishes.
michael@0 279 this.command_id = null;
michael@0 280 }
michael@0 281 },
michael@0 282
michael@0 283 /**
michael@0 284 * Send a value to client
michael@0 285 *
michael@0 286 * @param object value
michael@0 287 * Value to send back to client
michael@0 288 * @param string command_id
michael@0 289 * Unique identifier assigned to the client's request.
michael@0 290 * Used to distinguish the asynchronous responses.
michael@0 291 */
michael@0 292 sendResponse: function MDA_sendResponse(value, command_id) {
michael@0 293 if (typeof(value) == 'undefined')
michael@0 294 value = null;
michael@0 295 this.sendToClient({from:this.actorID, value: value}, command_id);
michael@0 296 },
michael@0 297
michael@0 298 sayHello: function MDA_sayHello() {
michael@0 299 this.conn.send({ from: "root",
michael@0 300 applicationType: "gecko",
michael@0 301 traits: [] });
michael@0 302 },
michael@0 303
michael@0 304 getMarionetteID: function MDA_getMarionette() {
michael@0 305 this.conn.send({ "from": "root", "id": this.actorID });
michael@0 306 },
michael@0 307
michael@0 308 /**
michael@0 309 * Send ack to client
michael@0 310 *
michael@0 311 * @param string command_id
michael@0 312 * Unique identifier assigned to the client's request.
michael@0 313 * Used to distinguish the asynchronous responses.
michael@0 314 */
michael@0 315 sendOk: function MDA_sendOk(command_id) {
michael@0 316 this.sendToClient({from:this.actorID, ok: true}, command_id);
michael@0 317 },
michael@0 318
michael@0 319 /**
michael@0 320 * Send error message to client
michael@0 321 *
michael@0 322 * @param string message
michael@0 323 * Error message
michael@0 324 * @param number status
michael@0 325 * Status number
michael@0 326 * @param string trace
michael@0 327 * Stack trace
michael@0 328 * @param string command_id
michael@0 329 * Unique identifier assigned to the client's request.
michael@0 330 * Used to distinguish the asynchronous responses.
michael@0 331 */
michael@0 332 sendError: function MDA_sendError(message, status, trace, command_id) {
michael@0 333 let error_msg = {message: message, status: status, stacktrace: trace};
michael@0 334 this.sendToClient({from:this.actorID, error: error_msg}, command_id);
michael@0 335 },
michael@0 336
michael@0 337 /**
michael@0 338 * Gets the current active window
michael@0 339 *
michael@0 340 * @return nsIDOMWindow
michael@0 341 */
michael@0 342 getCurrentWindow: function MDA_getCurrentWindow() {
michael@0 343 let type = null;
michael@0 344 if (this.curFrame == null) {
michael@0 345 if (this.curBrowser == null) {
michael@0 346 if (this.context == "content") {
michael@0 347 type = 'navigator:browser';
michael@0 348 }
michael@0 349 return Services.wm.getMostRecentWindow(type);
michael@0 350 }
michael@0 351 else {
michael@0 352 return this.curBrowser.window;
michael@0 353 }
michael@0 354 }
michael@0 355 else {
michael@0 356 return this.curFrame;
michael@0 357 }
michael@0 358 },
michael@0 359
michael@0 360 /**
michael@0 361 * Gets the the window enumerator
michael@0 362 *
michael@0 363 * @return nsISimpleEnumerator
michael@0 364 */
michael@0 365 getWinEnumerator: function MDA_getWinEnumerator() {
michael@0 366 let type = null;
michael@0 367 if (appName != "B2G" && this.context == "content") {
michael@0 368 type = 'navigator:browser';
michael@0 369 }
michael@0 370 return Services.wm.getEnumerator(type);
michael@0 371 },
michael@0 372
michael@0 373 /**
michael@0 374 * Create a new BrowserObj for window and add to known browsers
michael@0 375 *
michael@0 376 * @param nsIDOMWindow win
michael@0 377 * Window for which we will create a BrowserObj
michael@0 378 *
michael@0 379 * @return string
michael@0 380 * Returns the unique server-assigned ID of the window
michael@0 381 */
michael@0 382 addBrowser: function MDA_addBrowser(win) {
michael@0 383 let browser = new BrowserObj(win, this);
michael@0 384 let winId = win.QueryInterface(Ci.nsIInterfaceRequestor).
michael@0 385 getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
michael@0 386 winId = winId + ((appName == "B2G") ? '-b2g' : '');
michael@0 387 this.browsers[winId] = browser;
michael@0 388 this.curBrowser = this.browsers[winId];
michael@0 389 if (this.curBrowser.elementManager.seenItems[winId] == undefined) {
michael@0 390 //add this to seenItems so we can guarantee the user will get winId as this window's id
michael@0 391 this.curBrowser.elementManager.seenItems[winId] = Cu.getWeakReference(win);
michael@0 392 }
michael@0 393 },
michael@0 394
michael@0 395 /**
michael@0 396 * Start a new session in a new browser.
michael@0 397 *
michael@0 398 * If newSession is true, we will switch focus to the start frame
michael@0 399 * when it registers. Also, if it is in desktop, then a new tab
michael@0 400 * with the start page uri (about:blank) will be opened.
michael@0 401 *
michael@0 402 * @param nsIDOMWindow win
michael@0 403 * Window whose browser we need to access
michael@0 404 * @param boolean newSession
michael@0 405 * True if this is the first time we're talking to this browser
michael@0 406 */
michael@0 407 startBrowser: function MDA_startBrowser(win, newSession) {
michael@0 408 this.mainFrame = win;
michael@0 409 this.curFrame = null;
michael@0 410 this.addBrowser(win);
michael@0 411 this.curBrowser.newSession = newSession;
michael@0 412 this.curBrowser.startSession(newSession, win, this.whenBrowserStarted.bind(this));
michael@0 413 },
michael@0 414
michael@0 415 /**
michael@0 416 * Callback invoked after a new session has been started in a browser.
michael@0 417 * Loads the Marionette frame script into the browser if needed.
michael@0 418 *
michael@0 419 * @param nsIDOMWindow win
michael@0 420 * Window whose browser we need to access
michael@0 421 * @param boolean newSession
michael@0 422 * True if this is the first time we're talking to this browser
michael@0 423 */
michael@0 424 whenBrowserStarted: function MDA_whenBrowserStarted(win, newSession) {
michael@0 425 try {
michael@0 426 if (!Services.prefs.getBoolPref("marionette.contentListener") || !newSession) {
michael@0 427 this.curBrowser.loadFrameScript(FRAME_SCRIPT, win);
michael@0 428 }
michael@0 429 }
michael@0 430 catch (e) {
michael@0 431 //there may not always be a content process
michael@0 432 logger.info("could not load listener into content for page: " + win.location.href);
michael@0 433 }
michael@0 434 utils.window = win;
michael@0 435 },
michael@0 436
michael@0 437 /**
michael@0 438 * Recursively get all labeled text
michael@0 439 *
michael@0 440 * @param nsIDOMElement el
michael@0 441 * The parent element
michael@0 442 * @param array lines
michael@0 443 * Array that holds the text lines
michael@0 444 */
michael@0 445 getVisibleText: function MDA_getVisibleText(el, lines) {
michael@0 446 let nodeName = el.nodeName;
michael@0 447 try {
michael@0 448 if (utils.isElementDisplayed(el)) {
michael@0 449 if (el.value) {
michael@0 450 lines.push(el.value);
michael@0 451 }
michael@0 452 for (var child in el.childNodes) {
michael@0 453 this.getVisibleText(el.childNodes[child], lines);
michael@0 454 };
michael@0 455 }
michael@0 456 }
michael@0 457 catch (e) {
michael@0 458 if (nodeName == "#text") {
michael@0 459 lines.push(el.textContent);
michael@0 460 }
michael@0 461 }
michael@0 462 },
michael@0 463
michael@0 464 getCommandId: function MDA_getCommandId() {
michael@0 465 return this.uuidGen.generateUUID().toString();
michael@0 466 },
michael@0 467
michael@0 468 /**
michael@0 469 * Given a file name, this will delete the file from the temp directory if it exists
michael@0 470 */
michael@0 471 deleteFile: function(filename) {
michael@0 472 let file = FileUtils.getFile('TmpD', [filename.toString()]);
michael@0 473 if (file.exists()) {
michael@0 474 file.remove(true);
michael@0 475 }
michael@0 476 },
michael@0 477
michael@0 478 /**
michael@0 479 * Marionette API:
michael@0 480 *
michael@0 481 * All methods implementing a command from the client should create a
michael@0 482 * command_id, and then use this command_id in all messages exchanged with
michael@0 483 * the frame scripts and with responses sent to the client. This prevents
michael@0 484 * commands and responses from getting out-of-sync, which can happen in
michael@0 485 * the case of execute_async calls that timeout and then later send a
michael@0 486 * response, and other situations. See bug 779011. See setScriptTimeout()
michael@0 487 * for a basic example.
michael@0 488 */
michael@0 489
michael@0 490 /**
michael@0 491 * Create a new session. This creates a new BrowserObj.
michael@0 492 *
michael@0 493 * In a desktop environment, this opens a new browser with
michael@0 494 * "about:blank" which subsequent commands will be sent to.
michael@0 495 *
michael@0 496 * This will send a hash map of supported capabilities to the client
michael@0 497 * as part of the Marionette:register IPC command in the
michael@0 498 * receiveMessage callback when a new browser is created.
michael@0 499 */
michael@0 500 newSession: function MDA_newSession() {
michael@0 501 this.command_id = this.getCommandId();
michael@0 502 this.newSessionCommandId = this.command_id;
michael@0 503
michael@0 504 this.scriptTimeout = 10000;
michael@0 505
michael@0 506 function waitForWindow() {
michael@0 507 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 508 let win = this.getCurrentWindow();
michael@0 509 if (!win ||
michael@0 510 (appName == "Firefox" && !win.gBrowser) ||
michael@0 511 (appName == "Fennec" && !win.BrowserApp)) {
michael@0 512 checkTimer.initWithCallback(waitForWindow.bind(this), 100,
michael@0 513 Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 514 }
michael@0 515 else {
michael@0 516 this.startBrowser(win, true);
michael@0 517 }
michael@0 518 }
michael@0 519
michael@0 520 if (!Services.prefs.getBoolPref("marionette.contentListener")) {
michael@0 521 waitForWindow.call(this);
michael@0 522 }
michael@0 523 else if ((appName != "Firefox") && (this.curBrowser == null)) {
michael@0 524 // If there is a content listener, then we just wake it up
michael@0 525 this.addBrowser(this.getCurrentWindow());
michael@0 526 this.curBrowser.startSession(false, this.getCurrentWindow(),
michael@0 527 this.whenBrowserStarted);
michael@0 528 this.messageManager.broadcastAsyncMessage("Marionette:restart", {});
michael@0 529 }
michael@0 530 else {
michael@0 531 this.sendError("Session already running", 500, null,
michael@0 532 this.command_id);
michael@0 533 }
michael@0 534 this.switchToGlobalMessageManager();
michael@0 535 },
michael@0 536
michael@0 537 /**
michael@0 538 * Send the current session's capabilities to the client.
michael@0 539 *
michael@0 540 * Capabilities informs the client of which WebDriver features are
michael@0 541 * supported by Firefox and Marionette. They are immutable for the
michael@0 542 * length of the session.
michael@0 543 *
michael@0 544 * The return value is an immutable map of string keys
michael@0 545 * ("capabilities") to values, which may be of types boolean,
michael@0 546 * numerical or string.
michael@0 547 */
michael@0 548 getSessionCapabilities: function MDA_getSessionCapabilities() {
michael@0 549 this.command_id = this.getCommandId();
michael@0 550
michael@0 551 let isB2G = appName == "B2G";
michael@0 552 let platformName = Services.appinfo.OS.toUpperCase();
michael@0 553
michael@0 554 let caps = {
michael@0 555 // Mandated capabilities
michael@0 556 "browserName": appName,
michael@0 557 "browserVersion": Services.appinfo.version,
michael@0 558 "platformName": platformName,
michael@0 559 "platformVersion": Services.appinfo.platformVersion,
michael@0 560
michael@0 561 // Supported features
michael@0 562 "cssSelectorsEnabled": true,
michael@0 563 "handlesAlerts": false,
michael@0 564 "javascriptEnabled": true,
michael@0 565 "nativeEvents": false,
michael@0 566 "rotatable": isB2G,
michael@0 567 "secureSsl": false,
michael@0 568 "takesElementScreenshot": true,
michael@0 569 "takesScreenshot": true,
michael@0 570
michael@0 571 // Selenium 2 compat
michael@0 572 "platform": platformName,
michael@0 573
michael@0 574 // Proprietary extensions
michael@0 575 "XULappId" : Services.appinfo.ID,
michael@0 576 "appBuildId" : Services.appinfo.appBuildID,
michael@0 577 "device": qemu == "1" ? "qemu" : (!device ? "desktop" : device),
michael@0 578 "version": Services.appinfo.version
michael@0 579 };
michael@0 580
michael@0 581 // eideticker (bug 965297) and mochitest (bug 965304)
michael@0 582 // compatibility. They only check for the presence of this
michael@0 583 // property and should so not be in caps if not on a B2G device.
michael@0 584 if (isB2G)
michael@0 585 caps.b2g = true;
michael@0 586
michael@0 587 this.sendResponse(caps, this.command_id);
michael@0 588 },
michael@0 589
michael@0 590 /**
michael@0 591 * Log message. Accepts user defined log-level.
michael@0 592 *
michael@0 593 * @param object aRequest
michael@0 594 * 'value' member holds log message
michael@0 595 * 'level' member hold log level
michael@0 596 */
michael@0 597 log: function MDA_log(aRequest) {
michael@0 598 this.command_id = this.getCommandId();
michael@0 599 this.marionetteLog.log(aRequest.parameters.value, aRequest.parameters.level);
michael@0 600 this.sendOk(this.command_id);
michael@0 601 },
michael@0 602
michael@0 603 /**
michael@0 604 * Return all logged messages.
michael@0 605 */
michael@0 606 getLogs: function MDA_getLogs() {
michael@0 607 this.command_id = this.getCommandId();
michael@0 608 this.sendResponse(this.marionetteLog.getLogs(), this.command_id);
michael@0 609 },
michael@0 610
michael@0 611 /**
michael@0 612 * Sets the context of the subsequent commands to be either 'chrome' or 'content'
michael@0 613 *
michael@0 614 * @param object aRequest
michael@0 615 * 'value' member holds the name of the context to be switched to
michael@0 616 */
michael@0 617 setContext: function MDA_setContext(aRequest) {
michael@0 618 this.command_id = this.getCommandId();
michael@0 619 this.logRequest("setContext", aRequest);
michael@0 620 let context = aRequest.parameters.value;
michael@0 621 if (context != "content" && context != "chrome") {
michael@0 622 this.sendError("invalid context", 500, null, this.command_id);
michael@0 623 }
michael@0 624 else {
michael@0 625 this.context = context;
michael@0 626 this.sendOk(this.command_id);
michael@0 627 }
michael@0 628 },
michael@0 629
michael@0 630 /**
michael@0 631 * Returns a chrome sandbox that can be used by the execute_foo functions.
michael@0 632 *
michael@0 633 * @param nsIDOMWindow aWindow
michael@0 634 * Window in which we will execute code
michael@0 635 * @param Marionette marionette
michael@0 636 * Marionette test instance
michael@0 637 * @param object args
michael@0 638 * Client given args
michael@0 639 * @return Sandbox
michael@0 640 * Returns the sandbox
michael@0 641 */
michael@0 642 createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args, specialPowers, command_id) {
michael@0 643 try {
michael@0 644 args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow);
michael@0 645 }
michael@0 646 catch(e) {
michael@0 647 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 648 return;
michael@0 649 }
michael@0 650
michael@0 651 let _chromeSandbox = new Cu.Sandbox(aWindow,
michael@0 652 { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''});
michael@0 653 _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
michael@0 654 _chromeSandbox.__marionetteParams = args;
michael@0 655 _chromeSandbox.testUtils = utils;
michael@0 656
michael@0 657 marionette.exports.forEach(function(fn) {
michael@0 658 try {
michael@0 659 _chromeSandbox[fn] = marionette[fn].bind(marionette);
michael@0 660 }
michael@0 661 catch(e) {
michael@0 662 _chromeSandbox[fn] = marionette[fn];
michael@0 663 }
michael@0 664 });
michael@0 665
michael@0 666 _chromeSandbox.isSystemMessageListenerReady =
michael@0 667 function() { return systemMessageListenerReady; }
michael@0 668
michael@0 669 if (specialPowers == true) {
michael@0 670 loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js",
michael@0 671 _chromeSandbox);
michael@0 672 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js",
michael@0 673 _chromeSandbox);
michael@0 674 loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js",
michael@0 675 _chromeSandbox);
michael@0 676 }
michael@0 677
michael@0 678 return _chromeSandbox;
michael@0 679 },
michael@0 680
michael@0 681 /**
michael@0 682 * Executes a script in the given sandbox.
michael@0 683 *
michael@0 684 * @param Sandbox sandbox
michael@0 685 * Sandbox in which the script will run
michael@0 686 * @param string script
michael@0 687 * The script to run
michael@0 688 * @param boolean directInject
michael@0 689 * If true, then the script will be run as is,
michael@0 690 * and not as a function body (as you would
michael@0 691 * do using the WebDriver spec)
michael@0 692 * @param boolean async
michael@0 693 * True if the script is asynchronous
michael@0 694 */
michael@0 695 executeScriptInSandbox: function MDA_executeScriptInSandbox(sandbox, script,
michael@0 696 directInject, async, command_id, timeout) {
michael@0 697
michael@0 698 if (directInject && async &&
michael@0 699 (timeout == null || timeout == 0)) {
michael@0 700 this.sendError("Please set a timeout", 21, null, command_id);
michael@0 701 return;
michael@0 702 }
michael@0 703
michael@0 704 if (this.importedScripts.exists()) {
michael@0 705 let stream = Cc["@mozilla.org/network/file-input-stream;1"].
michael@0 706 createInstance(Ci.nsIFileInputStream);
michael@0 707 stream.init(this.importedScripts, -1, 0, 0);
michael@0 708 let data = NetUtil.readInputStreamToString(stream, stream.available());
michael@0 709 script = data + script;
michael@0 710 }
michael@0 711
michael@0 712 let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
michael@0 713
michael@0 714 if (directInject && !async &&
michael@0 715 (res == undefined || res.passed == undefined)) {
michael@0 716 this.sendError("finish() not called", 500, null, command_id);
michael@0 717 return;
michael@0 718 }
michael@0 719
michael@0 720 if (!async) {
michael@0 721 this.sendResponse(this.curBrowser.elementManager.wrapValue(res),
michael@0 722 command_id);
michael@0 723 }
michael@0 724 },
michael@0 725
michael@0 726 /**
michael@0 727 * Execute the given script either as a function body (executeScript)
michael@0 728 * or directly (for 'mochitest' like JS Marionette tests)
michael@0 729 *
michael@0 730 * @param object aRequest
michael@0 731 * 'script' member is the script to run
michael@0 732 * 'args' member holds the arguments to the script
michael@0 733 * @param boolean directInject
michael@0 734 * if true, it will be run directly and not as a
michael@0 735 * function body
michael@0 736 */
michael@0 737 execute: function MDA_execute(aRequest, directInject) {
michael@0 738 let inactivityTimeout = aRequest.parameters.inactivityTimeout;
michael@0 739 let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
michael@0 740 let command_id = this.command_id = this.getCommandId();
michael@0 741 let script;
michael@0 742 this.logRequest("execute", aRequest);
michael@0 743 if (aRequest.parameters.newSandbox == undefined) {
michael@0 744 //if client does not send a value in newSandbox,
michael@0 745 //then they expect the same behaviour as webdriver
michael@0 746 aRequest.parameters.newSandbox = true;
michael@0 747 }
michael@0 748 if (this.context == "content") {
michael@0 749 this.sendAsync("executeScript",
michael@0 750 {
michael@0 751 script: aRequest.parameters.script,
michael@0 752 args: aRequest.parameters.args,
michael@0 753 newSandbox: aRequest.parameters.newSandbox,
michael@0 754 timeout: timeout,
michael@0 755 specialPowers: aRequest.parameters.specialPowers,
michael@0 756 filename: aRequest.parameters.filename,
michael@0 757 line: aRequest.parameters.line
michael@0 758 },
michael@0 759 command_id);
michael@0 760 return;
michael@0 761 }
michael@0 762
michael@0 763 // handle the inactivity timeout
michael@0 764 let that = this;
michael@0 765 if (inactivityTimeout) {
michael@0 766 let inactivityTimeoutHandler = function(message, status) {
michael@0 767 let error_msg = {message: value, status: status};
michael@0 768 that.sendToClient({from: that.actorID, error: error_msg},
michael@0 769 marionette.command_id);
michael@0 770 };
michael@0 771 let setTimer = function() {
michael@0 772 that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 773 if (that.inactivityTimer != null) {
michael@0 774 that.inactivityTimer.initWithCallback(function() {
michael@0 775 inactivityTimeoutHandler("timed out due to inactivity", 28);
michael@0 776 }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT);
michael@0 777 }
michael@0 778 }
michael@0 779 setTimer();
michael@0 780 this.heartbeatCallback = function resetInactivityTimer() {
michael@0 781 that.inactivityTimer.cancel();
michael@0 782 setTimer();
michael@0 783 }
michael@0 784 }
michael@0 785
michael@0 786 let curWindow = this.getCurrentWindow();
michael@0 787 let marionette = new Marionette(this, curWindow, "chrome",
michael@0 788 this.marionetteLog,
michael@0 789 timeout, this.heartbeatCallback, this.testName);
michael@0 790 let _chromeSandbox = this.createExecuteSandbox(curWindow,
michael@0 791 marionette,
michael@0 792 aRequest.parameters.args,
michael@0 793 aRequest.parameters.specialPowers,
michael@0 794 command_id);
michael@0 795 if (!_chromeSandbox)
michael@0 796 return;
michael@0 797
michael@0 798 try {
michael@0 799 _chromeSandbox.finish = function chromeSandbox_finish() {
michael@0 800 if (that.inactivityTimer != null) {
michael@0 801 that.inactivityTimer.cancel();
michael@0 802 }
michael@0 803 return marionette.generate_results();
michael@0 804 };
michael@0 805
michael@0 806 if (directInject) {
michael@0 807 script = aRequest.parameters.script;
michael@0 808 }
michael@0 809 else {
michael@0 810 script = "let func = function() {" +
michael@0 811 aRequest.parameters.script +
michael@0 812 "};" +
michael@0 813 "func.apply(null, __marionetteParams);";
michael@0 814 }
michael@0 815 this.executeScriptInSandbox(_chromeSandbox, script, directInject,
michael@0 816 false, command_id, timeout);
michael@0 817 }
michael@0 818 catch (e) {
michael@0 819 let error = createStackMessage(e,
michael@0 820 "execute_script",
michael@0 821 aRequest.parameters.filename,
michael@0 822 aRequest.parameters.line,
michael@0 823 script);
michael@0 824 this.sendError(error[0], 17, error[1], command_id);
michael@0 825 }
michael@0 826 },
michael@0 827
michael@0 828 /**
michael@0 829 * Set the timeout for asynchronous script execution
michael@0 830 *
michael@0 831 * @param object aRequest
michael@0 832 * 'ms' member is time in milliseconds to set timeout
michael@0 833 */
michael@0 834 setScriptTimeout: function MDA_setScriptTimeout(aRequest) {
michael@0 835 this.command_id = this.getCommandId();
michael@0 836 let timeout = parseInt(aRequest.parameters.ms);
michael@0 837 if(isNaN(timeout)){
michael@0 838 this.sendError("Not a Number", 500, null, this.command_id);
michael@0 839 }
michael@0 840 else {
michael@0 841 this.scriptTimeout = timeout;
michael@0 842 this.sendOk(this.command_id);
michael@0 843 }
michael@0 844 },
michael@0 845
michael@0 846 /**
michael@0 847 * execute pure JS script. Used to execute 'mochitest'-style Marionette tests.
michael@0 848 *
michael@0 849 * @param object aRequest
michael@0 850 * 'script' member holds the script to execute
michael@0 851 * 'args' member holds the arguments to the script
michael@0 852 * 'timeout' member will be used as the script timeout if it is given
michael@0 853 */
michael@0 854 executeJSScript: function MDA_executeJSScript(aRequest) {
michael@0 855 let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
michael@0 856 let command_id = this.command_id = this.getCommandId();
michael@0 857
michael@0 858 //all pure JS scripts will need to call Marionette.finish() to complete the test.
michael@0 859 if (aRequest.newSandbox == undefined) {
michael@0 860 //if client does not send a value in newSandbox,
michael@0 861 //then they expect the same behaviour as webdriver
michael@0 862 aRequest.newSandbox = true;
michael@0 863 }
michael@0 864 if (this.context == "chrome") {
michael@0 865 if (aRequest.parameters.async) {
michael@0 866 this.executeWithCallback(aRequest, aRequest.parameters.async);
michael@0 867 }
michael@0 868 else {
michael@0 869 this.execute(aRequest, true);
michael@0 870 }
michael@0 871 }
michael@0 872 else {
michael@0 873 this.sendAsync("executeJSScript",
michael@0 874 {
michael@0 875 script: aRequest.parameters.script,
michael@0 876 args: aRequest.parameters.args,
michael@0 877 newSandbox: aRequest.parameters.newSandbox,
michael@0 878 async: aRequest.parameters.async,
michael@0 879 timeout: timeout,
michael@0 880 inactivityTimeout: aRequest.parameters.inactivityTimeout,
michael@0 881 specialPowers: aRequest.parameters.specialPowers,
michael@0 882 filename: aRequest.parameters.filename,
michael@0 883 line: aRequest.parameters.line,
michael@0 884 },
michael@0 885 command_id);
michael@0 886 }
michael@0 887 },
michael@0 888
michael@0 889 /**
michael@0 890 * This function is used by executeAsync and executeJSScript to execute a script
michael@0 891 * in a sandbox.
michael@0 892 *
michael@0 893 * For executeJSScript, it will return a message only when the finish() method is called.
michael@0 894 * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
michael@0 895 * method is called, or if it times out.
michael@0 896 *
michael@0 897 * @param object aRequest
michael@0 898 * 'script' member holds the script to execute
michael@0 899 * 'args' member holds the arguments for the script
michael@0 900 * @param boolean directInject
michael@0 901 * if true, it will be run directly and not as a
michael@0 902 * function body
michael@0 903 */
michael@0 904 executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
michael@0 905 let inactivityTimeout = aRequest.parameters.inactivityTimeout;
michael@0 906 let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
michael@0 907 let command_id = this.command_id = this.getCommandId();
michael@0 908 let script;
michael@0 909 this.logRequest("executeWithCallback", aRequest);
michael@0 910 if (aRequest.parameters.newSandbox == undefined) {
michael@0 911 //if client does not send a value in newSandbox,
michael@0 912 //then they expect the same behaviour as webdriver
michael@0 913 aRequest.parameters.newSandbox = true;
michael@0 914 }
michael@0 915
michael@0 916 if (this.context == "content") {
michael@0 917 this.sendAsync("executeAsyncScript",
michael@0 918 {
michael@0 919 script: aRequest.parameters.script,
michael@0 920 args: aRequest.parameters.args,
michael@0 921 id: this.command_id,
michael@0 922 newSandbox: aRequest.parameters.newSandbox,
michael@0 923 timeout: timeout,
michael@0 924 inactivityTimeout: inactivityTimeout,
michael@0 925 specialPowers: aRequest.parameters.specialPowers,
michael@0 926 filename: aRequest.parameters.filename,
michael@0 927 line: aRequest.parameters.line
michael@0 928 },
michael@0 929 command_id);
michael@0 930 return;
michael@0 931 }
michael@0 932
michael@0 933 // handle the inactivity timeout
michael@0 934 let that = this;
michael@0 935 if (inactivityTimeout) {
michael@0 936 this.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 937 if (this.inactivityTimer != null) {
michael@0 938 this.inactivityTimer.initWithCallback(function() {
michael@0 939 chromeAsyncReturnFunc("timed out due to inactivity", 28);
michael@0 940 }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT);
michael@0 941 }
michael@0 942 this.heartbeatCallback = function resetInactivityTimer() {
michael@0 943 that.inactivityTimer.cancel();
michael@0 944 that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 945 if (that.inactivityTimer != null) {
michael@0 946 that.inactivityTimer.initWithCallback(function() {
michael@0 947 chromeAsyncReturnFunc("timed out due to inactivity", 28);
michael@0 948 }, inactivityTimeout, Ci.nsITimer.TYPE_ONESHOT);
michael@0 949 }
michael@0 950 }
michael@0 951 }
michael@0 952
michael@0 953 let curWindow = this.getCurrentWindow();
michael@0 954 let original_onerror = curWindow.onerror;
michael@0 955 let that = this;
michael@0 956 that.timeout = timeout;
michael@0 957 let marionette = new Marionette(this, curWindow, "chrome",
michael@0 958 this.marionetteLog,
michael@0 959 timeout, this.heartbeatCallback, this.testName);
michael@0 960 marionette.command_id = this.command_id;
michael@0 961
michael@0 962 function chromeAsyncReturnFunc(value, status, stacktrace) {
michael@0 963 if (that._emu_cbs && Object.keys(that._emu_cbs).length) {
michael@0 964 value = "Emulator callback still pending when finish() called";
michael@0 965 status = 500;
michael@0 966 that._emu_cbs = null;
michael@0 967 }
michael@0 968
michael@0 969 if (value == undefined)
michael@0 970 value = null;
michael@0 971 if (that.command_id == marionette.command_id) {
michael@0 972 if (that.timer != null) {
michael@0 973 that.timer.cancel();
michael@0 974 that.timer = null;
michael@0 975 }
michael@0 976
michael@0 977 curWindow.onerror = original_onerror;
michael@0 978
michael@0 979 if (status == 0 || status == undefined) {
michael@0 980 that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status},
michael@0 981 marionette.command_id);
michael@0 982 }
michael@0 983 else {
michael@0 984 let error_msg = {message: value, status: status, stacktrace: stacktrace};
michael@0 985 that.sendToClient({from: that.actorID, error: error_msg},
michael@0 986 marionette.command_id);
michael@0 987 }
michael@0 988 }
michael@0 989 if (that.inactivityTimer != null) {
michael@0 990 that.inactivityTimer.cancel();
michael@0 991 }
michael@0 992 }
michael@0 993
michael@0 994 curWindow.onerror = function (errorMsg, url, lineNumber) {
michael@0 995 chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17);
michael@0 996 return true;
michael@0 997 };
michael@0 998
michael@0 999 function chromeAsyncFinish() {
michael@0 1000 chromeAsyncReturnFunc(marionette.generate_results(), 0);
michael@0 1001 }
michael@0 1002
michael@0 1003 let _chromeSandbox = this.createExecuteSandbox(curWindow,
michael@0 1004 marionette,
michael@0 1005 aRequest.parameters.args,
michael@0 1006 aRequest.parameters.specialPowers,
michael@0 1007 command_id);
michael@0 1008 if (!_chromeSandbox)
michael@0 1009 return;
michael@0 1010
michael@0 1011 try {
michael@0 1012
michael@0 1013 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 1014 if (this.timer != null) {
michael@0 1015 this.timer.initWithCallback(function() {
michael@0 1016 chromeAsyncReturnFunc("timed out", 28);
michael@0 1017 }, that.timeout, Ci.nsITimer.TYPE_ONESHOT);
michael@0 1018 }
michael@0 1019
michael@0 1020 _chromeSandbox.returnFunc = chromeAsyncReturnFunc;
michael@0 1021 _chromeSandbox.finish = chromeAsyncFinish;
michael@0 1022
michael@0 1023 if (directInject) {
michael@0 1024 script = aRequest.parameters.script;
michael@0 1025 }
michael@0 1026 else {
michael@0 1027 script = '__marionetteParams.push(returnFunc);'
michael@0 1028 + 'let marionetteScriptFinished = returnFunc;'
michael@0 1029 + 'let __marionetteFunc = function() {' + aRequest.parameters.script + '};'
michael@0 1030 + '__marionetteFunc.apply(null, __marionetteParams);';
michael@0 1031 }
michael@0 1032
michael@0 1033 this.executeScriptInSandbox(_chromeSandbox, script, directInject,
michael@0 1034 true, command_id, timeout);
michael@0 1035 } catch (e) {
michael@0 1036 let error = createStackMessage(e,
michael@0 1037 "execute_async_script",
michael@0 1038 aRequest.parameters.filename,
michael@0 1039 aRequest.parameters.line,
michael@0 1040 script);
michael@0 1041 chromeAsyncReturnFunc(error[0], 17, error[1]);
michael@0 1042 }
michael@0 1043 },
michael@0 1044
michael@0 1045 /**
michael@0 1046 * Navigate to to given URL.
michael@0 1047 *
michael@0 1048 * This will follow redirects issued by the server. When the method
michael@0 1049 * returns is based on the page load strategy that the user has
michael@0 1050 * selected.
michael@0 1051 *
michael@0 1052 * Documents that contain a META tag with the "http-equiv" attribute
michael@0 1053 * set to "refresh" will return if the timeout is greater than 1
michael@0 1054 * second and the other criteria for determining whether a page is
michael@0 1055 * loaded are met. When the refresh period is 1 second or less and
michael@0 1056 * the page load strategy is "normal" or "conservative", it will
michael@0 1057 * wait for the page to complete loading before returning.
michael@0 1058 *
michael@0 1059 * If any modal dialog box, such as those opened on
michael@0 1060 * window.onbeforeunload or window.alert, is opened at any point in
michael@0 1061 * the page load, it will return immediately.
michael@0 1062 *
michael@0 1063 * If a 401 response is seen by the browser, it will return
michael@0 1064 * immediately. That is, if BASIC, DIGEST, NTLM or similar
michael@0 1065 * authentication is required, the page load is assumed to be
michael@0 1066 * complete. This does not include FORM-based authentication.
michael@0 1067 *
michael@0 1068 * @param object aRequest where <code>url</code> property holds the
michael@0 1069 * URL to navigate to
michael@0 1070 */
michael@0 1071 get: function MDA_get(aRequest) {
michael@0 1072 let command_id = this.command_id = this.getCommandId();
michael@0 1073 if (this.context != "chrome") {
michael@0 1074 aRequest.command_id = command_id;
michael@0 1075 aRequest.parameters.pageTimeout = this.pageTimeout;
michael@0 1076 this.sendAsync("get", aRequest.parameters, command_id);
michael@0 1077 return;
michael@0 1078 }
michael@0 1079
michael@0 1080 this.getCurrentWindow().location.href = aRequest.parameters.url;
michael@0 1081 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 1082 let start = new Date().getTime();
michael@0 1083 let end = null;
michael@0 1084
michael@0 1085 function checkLoad() {
michael@0 1086 end = new Date().getTime();
michael@0 1087 let elapse = end - start;
michael@0 1088 if (this.pageTimeout == null || elapse <= this.pageTimeout){
michael@0 1089 if (curWindow.document.readyState == "complete") {
michael@0 1090 sendOk(command_id);
michael@0 1091 return;
michael@0 1092 }
michael@0 1093 else{
michael@0 1094 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 1095 }
michael@0 1096 }
michael@0 1097 else{
michael@0 1098 sendError("Error loading page", 13, null, command_id);
michael@0 1099 return;
michael@0 1100 }
michael@0 1101 }
michael@0 1102 checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 1103 },
michael@0 1104
michael@0 1105 /**
michael@0 1106 * Get a string representing the current URL.
michael@0 1107 *
michael@0 1108 * On Desktop this returns a string representation of the URL of the
michael@0 1109 * current top level browsing context. This is equivalent to
michael@0 1110 * document.location.href.
michael@0 1111 *
michael@0 1112 * When in the context of the chrome, this returns the canonical URL
michael@0 1113 * of the current resource.
michael@0 1114 */
michael@0 1115 getCurrentUrl: function MDA_getCurrentUrl() {
michael@0 1116 this.command_id = this.getCommandId();
michael@0 1117 if (this.context == "chrome") {
michael@0 1118 this.sendResponse(this.getCurrentWindow().location.href, this.command_id);
michael@0 1119 }
michael@0 1120 else {
michael@0 1121 this.sendAsync("getCurrentUrl", {}, this.command_id);
michael@0 1122 }
michael@0 1123 },
michael@0 1124
michael@0 1125 /**
michael@0 1126 * Gets the current title of the window
michael@0 1127 */
michael@0 1128 getTitle: function MDA_getTitle() {
michael@0 1129 this.command_id = this.getCommandId();
michael@0 1130 if (this.context == "chrome"){
michael@0 1131 var curWindow = this.getCurrentWindow();
michael@0 1132 var title = curWindow.document.documentElement.getAttribute('title');
michael@0 1133 this.sendResponse(title, this.command_id);
michael@0 1134 }
michael@0 1135 else {
michael@0 1136 this.sendAsync("getTitle", {}, this.command_id);
michael@0 1137 }
michael@0 1138 },
michael@0 1139
michael@0 1140 /**
michael@0 1141 * Gets the current type of the window
michael@0 1142 */
michael@0 1143 getWindowType: function MDA_getWindowType() {
michael@0 1144 this.command_id = this.getCommandId();
michael@0 1145 var curWindow = this.getCurrentWindow();
michael@0 1146 var type = curWindow.document.documentElement.getAttribute('windowtype');
michael@0 1147 this.sendResponse(type, this.command_id);
michael@0 1148 },
michael@0 1149
michael@0 1150 /**
michael@0 1151 * Gets the page source of the content document
michael@0 1152 */
michael@0 1153 getPageSource: function MDA_getPageSource(){
michael@0 1154 this.command_id = this.getCommandId();
michael@0 1155 if (this.context == "chrome"){
michael@0 1156 let curWindow = this.getCurrentWindow();
michael@0 1157 let XMLSerializer = curWindow.XMLSerializer;
michael@0 1158 let pageSource = new XMLSerializer().serializeToString(curWindow.document);
michael@0 1159 this.sendResponse(pageSource, this.command_id);
michael@0 1160 }
michael@0 1161 else {
michael@0 1162 this.sendAsync("getPageSource", {}, this.command_id);
michael@0 1163 }
michael@0 1164 },
michael@0 1165
michael@0 1166 /**
michael@0 1167 * Go back in history
michael@0 1168 */
michael@0 1169 goBack: function MDA_goBack() {
michael@0 1170 this.command_id = this.getCommandId();
michael@0 1171 this.sendAsync("goBack", {}, this.command_id);
michael@0 1172 },
michael@0 1173
michael@0 1174 /**
michael@0 1175 * Go forward in history
michael@0 1176 */
michael@0 1177 goForward: function MDA_goForward() {
michael@0 1178 this.command_id = this.getCommandId();
michael@0 1179 this.sendAsync("goForward", {}, this.command_id);
michael@0 1180 },
michael@0 1181
michael@0 1182 /**
michael@0 1183 * Refresh the page
michael@0 1184 */
michael@0 1185 refresh: function MDA_refresh() {
michael@0 1186 this.command_id = this.getCommandId();
michael@0 1187 this.sendAsync("refresh", {}, this.command_id);
michael@0 1188 },
michael@0 1189
michael@0 1190 /**
michael@0 1191 * Get the current window's handle.
michael@0 1192 *
michael@0 1193 * Return an opaque server-assigned identifier to this window that
michael@0 1194 * uniquely identifies it within this Marionette instance. This can
michael@0 1195 * be used to switch to this window at a later point.
michael@0 1196 *
michael@0 1197 * @return unique window handle (string)
michael@0 1198 */
michael@0 1199 getWindowHandle: function MDA_getWindowHandle() {
michael@0 1200 this.command_id = this.getCommandId();
michael@0 1201 for (let i in this.browsers) {
michael@0 1202 if (this.curBrowser == this.browsers[i]) {
michael@0 1203 this.sendResponse(i, this.command_id);
michael@0 1204 return;
michael@0 1205 }
michael@0 1206 }
michael@0 1207 },
michael@0 1208
michael@0 1209 /**
michael@0 1210 * Get list of windows in the current context.
michael@0 1211 *
michael@0 1212 * If called in the content context it will return a list of
michael@0 1213 * references to all available browser windows. Called in the
michael@0 1214 * chrome context, it will list all available windows, not just
michael@0 1215 * browser windows (e.g. not just navigator.browser).
michael@0 1216 *
michael@0 1217 * Each window handle is assigned by the server, and the array of
michael@0 1218 * strings returned does not have a guaranteed ordering.
michael@0 1219 *
michael@0 1220 * @return unordered array of unique window handles as strings
michael@0 1221 */
michael@0 1222 getWindowHandles: function MDA_getWindowHandles() {
michael@0 1223 this.command_id = this.getCommandId();
michael@0 1224 let res = [];
michael@0 1225 let winEn = this.getWinEnumerator();
michael@0 1226 while (winEn.hasMoreElements()) {
michael@0 1227 let foundWin = winEn.getNext();
michael@0 1228 let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 1229 .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
michael@0 1230 winId = winId + ((appName == "B2G") ? "-b2g" : "");
michael@0 1231 res.push(winId);
michael@0 1232 }
michael@0 1233 this.sendResponse(res, this.command_id);
michael@0 1234 },
michael@0 1235
michael@0 1236 /**
michael@0 1237 * Switch to a window based on name or server-assigned id.
michael@0 1238 * Searches based on name, then id.
michael@0 1239 *
michael@0 1240 * @param object aRequest
michael@0 1241 * 'name' member holds the name or id of the window to switch to
michael@0 1242 */
michael@0 1243 switchToWindow: function MDA_switchToWindow(aRequest) {
michael@0 1244 let command_id = this.command_id = this.getCommandId();
michael@0 1245 let winEn = this.getWinEnumerator();
michael@0 1246 while(winEn.hasMoreElements()) {
michael@0 1247 let foundWin = winEn.getNext();
michael@0 1248 let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 1249 .getInterface(Ci.nsIDOMWindowUtils)
michael@0 1250 .outerWindowID;
michael@0 1251 winId = winId + ((appName == "B2G") ? '-b2g' : '');
michael@0 1252 if (aRequest.parameters.name == foundWin.name || aRequest.parameters.name == winId) {
michael@0 1253 if (this.browsers[winId] == undefined) {
michael@0 1254 //enable Marionette in that browser window
michael@0 1255 this.startBrowser(foundWin, false);
michael@0 1256 }
michael@0 1257 else {
michael@0 1258 utils.window = foundWin;
michael@0 1259 this.curBrowser = this.browsers[winId];
michael@0 1260 }
michael@0 1261 this.sendOk(command_id);
michael@0 1262 return;
michael@0 1263 }
michael@0 1264 }
michael@0 1265 this.sendError("Unable to locate window " + aRequest.parameters.name, 23, null,
michael@0 1266 command_id);
michael@0 1267 },
michael@0 1268
michael@0 1269 getActiveFrame: function MDA_getActiveFrame() {
michael@0 1270 this.command_id = this.getCommandId();
michael@0 1271
michael@0 1272 if (this.context == "chrome") {
michael@0 1273 if (this.curFrame) {
michael@0 1274 let frameUid = this.curBrowser.elementManager.addToKnownElements(this.curFrame.frameElement);
michael@0 1275 this.sendResponse(frameUid, this.command_id);
michael@0 1276 } else {
michael@0 1277 // no current frame, we're at toplevel
michael@0 1278 this.sendResponse(null, this.command_id);
michael@0 1279 }
michael@0 1280 } else {
michael@0 1281 // not chrome
michael@0 1282 this.sendResponse(this.currentFrameElement, this.command_id);
michael@0 1283 }
michael@0 1284 },
michael@0 1285
michael@0 1286 /**
michael@0 1287 * Switch to a given frame within the current window
michael@0 1288 *
michael@0 1289 * @param object aRequest
michael@0 1290 * 'element' is the element to switch to
michael@0 1291 * 'id' if element is not set, then this
michael@0 1292 * holds either the id, name or index
michael@0 1293 * of the frame to switch to
michael@0 1294 */
michael@0 1295 switchToFrame: function MDA_switchToFrame(aRequest) {
michael@0 1296 let command_id = this.command_id = this.getCommandId();
michael@0 1297 this.logRequest("switchToFrame", aRequest);
michael@0 1298 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 1299 let curWindow = this.getCurrentWindow();
michael@0 1300 let checkLoad = function() {
michael@0 1301 let errorRegex = /about:.+(error)|(blocked)\?/;
michael@0 1302 let curWindow = this.getCurrentWindow();
michael@0 1303 if (curWindow.document.readyState == "complete") {
michael@0 1304 this.sendOk(command_id);
michael@0 1305 return;
michael@0 1306 }
michael@0 1307 else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)) {
michael@0 1308 this.sendError("Error loading page", 13, null, command_id);
michael@0 1309 return;
michael@0 1310 }
michael@0 1311
michael@0 1312 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 1313 }
michael@0 1314 if (this.context == "chrome") {
michael@0 1315 let foundFrame = null;
michael@0 1316 if ((aRequest.parameters.id == null) && (aRequest.parameters.element == null)) {
michael@0 1317 this.curFrame = null;
michael@0 1318 if (aRequest.parameters.focus) {
michael@0 1319 this.mainFrame.focus();
michael@0 1320 }
michael@0 1321 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 1322 return;
michael@0 1323 }
michael@0 1324 if (aRequest.parameters.element != undefined) {
michael@0 1325 if (this.curBrowser.elementManager.seenItems[aRequest.parameters.element]) {
michael@0 1326 let wantedFrame = this.curBrowser.elementManager.getKnownElement(aRequest.parameters.element, curWindow); //HTMLIFrameElement
michael@0 1327 let frames = curWindow.document.getElementsByTagName("iframe");
michael@0 1328 let numFrames = frames.length;
michael@0 1329 for (let i = 0; i < numFrames; i++) {
michael@0 1330 if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) {
michael@0 1331 curWindow = frames[i].contentWindow;
michael@0 1332 this.curFrame = curWindow;
michael@0 1333 if (aRequest.parameters.focus) {
michael@0 1334 this.curFrame.focus();
michael@0 1335 }
michael@0 1336 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 1337 return;
michael@0 1338 }
michael@0 1339 }
michael@0 1340 }
michael@0 1341 }
michael@0 1342 switch(typeof(aRequest.parameters.id)) {
michael@0 1343 case "string" :
michael@0 1344 let foundById = null;
michael@0 1345 let frames = curWindow.document.getElementsByTagName("iframe");
michael@0 1346 let numFrames = frames.length;
michael@0 1347 for (let i = 0; i < numFrames; i++) {
michael@0 1348 //give precedence to name
michael@0 1349 let frame = frames[i];
michael@0 1350 if (frame.getAttribute("name") == aRequest.parameters.id) {
michael@0 1351 foundFrame = i;
michael@0 1352 curWindow = frame.contentWindow;
michael@0 1353 break;
michael@0 1354 } else if ((foundById == null) && (frame.id == aRequest.parameters.id)) {
michael@0 1355 foundById = i;
michael@0 1356 }
michael@0 1357 }
michael@0 1358 if ((foundFrame == null) && (foundById != null)) {
michael@0 1359 foundFrame = foundById;
michael@0 1360 curWindow = frames[foundById].contentWindow;
michael@0 1361 }
michael@0 1362 break;
michael@0 1363 case "number":
michael@0 1364 if (curWindow.frames[aRequest.parameters.id] != undefined) {
michael@0 1365 foundFrame = aRequest.parameters.id;
michael@0 1366 curWindow = curWindow.frames[foundFrame].frameElement.contentWindow;
michael@0 1367 }
michael@0 1368 break;
michael@0 1369 }
michael@0 1370 if (foundFrame != null) {
michael@0 1371 this.curFrame = curWindow;
michael@0 1372 if (aRequest.parameters.focus) {
michael@0 1373 this.curFrame.focus();
michael@0 1374 }
michael@0 1375 checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 1376 } else {
michael@0 1377 this.sendError("Unable to locate frame: " + aRequest.parameters.id, 8, null,
michael@0 1378 command_id);
michael@0 1379 }
michael@0 1380 }
michael@0 1381 else {
michael@0 1382 if ((!aRequest.parameters.id) && (!aRequest.parameters.element) &&
michael@0 1383 (this.curBrowser.frameManager.currentRemoteFrame !== null)) {
michael@0 1384 // We're currently using a ChromeMessageSender for a remote frame, so this
michael@0 1385 // request indicates we need to switch back to the top-level (parent) frame.
michael@0 1386 // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so
michael@0 1387 // we send the message to the right listener.
michael@0 1388 this.switchToGlobalMessageManager();
michael@0 1389 }
michael@0 1390 aRequest.command_id = command_id;
michael@0 1391 this.sendAsync("switchToFrame", aRequest.parameters, command_id);
michael@0 1392 }
michael@0 1393 },
michael@0 1394
michael@0 1395 /**
michael@0 1396 * Set timeout for searching for elements
michael@0 1397 *
michael@0 1398 * @param object aRequest
michael@0 1399 * 'ms' holds the search timeout in milliseconds
michael@0 1400 */
michael@0 1401 setSearchTimeout: function MDA_setSearchTimeout(aRequest) {
michael@0 1402 this.command_id = this.getCommandId();
michael@0 1403 let timeout = parseInt(aRequest.parameters.ms);
michael@0 1404 if (isNaN(timeout)) {
michael@0 1405 this.sendError("Not a Number", 500, null, this.command_id);
michael@0 1406 }
michael@0 1407 else {
michael@0 1408 this.searchTimeout = timeout;
michael@0 1409 this.sendOk(this.command_id);
michael@0 1410 }
michael@0 1411 },
michael@0 1412
michael@0 1413 /**
michael@0 1414 * Set timeout for page loading, searching and scripts
michael@0 1415 *
michael@0 1416 * @param object aRequest
michael@0 1417 * 'type' hold the type of timeout
michael@0 1418 * 'ms' holds the timeout in milliseconds
michael@0 1419 */
michael@0 1420 timeouts: function MDA_timeouts(aRequest){
michael@0 1421 /*setTimeout*/
michael@0 1422 this.command_id = this.getCommandId();
michael@0 1423 let timeout_type = aRequest.parameters.type;
michael@0 1424 let timeout = parseInt(aRequest.parameters.ms);
michael@0 1425 if (isNaN(timeout)) {
michael@0 1426 this.sendError("Not a Number", 500, null, this.command_id);
michael@0 1427 }
michael@0 1428 else {
michael@0 1429 if (timeout_type == "implicit") {
michael@0 1430 this.setSearchTimeout(aRequest);
michael@0 1431 }
michael@0 1432 else if (timeout_type == "script") {
michael@0 1433 this.setScriptTimeout(aRequest);
michael@0 1434 }
michael@0 1435 else {
michael@0 1436 this.pageTimeout = timeout;
michael@0 1437 this.sendOk(this.command_id);
michael@0 1438 }
michael@0 1439 }
michael@0 1440 },
michael@0 1441
michael@0 1442 /**
michael@0 1443 * Single Tap
michael@0 1444 *
michael@0 1445 * @param object aRequest
michael@0 1446 'element' represents the ID of the element to single tap on
michael@0 1447 */
michael@0 1448 singleTap: function MDA_singleTap(aRequest) {
michael@0 1449 this.command_id = this.getCommandId();
michael@0 1450 let serId = aRequest.parameters.id;
michael@0 1451 let x = aRequest.parameters.x;
michael@0 1452 let y = aRequest.parameters.y;
michael@0 1453 if (this.context == "chrome") {
michael@0 1454 this.sendError("Command 'singleTap' is not available in chrome context", 500, null, this.command_id);
michael@0 1455 }
michael@0 1456 else {
michael@0 1457 this.sendAsync("singleTap",
michael@0 1458 {
michael@0 1459 id: serId,
michael@0 1460 corx: x,
michael@0 1461 cory: y
michael@0 1462 },
michael@0 1463 this.command_id);
michael@0 1464 }
michael@0 1465 },
michael@0 1466
michael@0 1467 /**
michael@0 1468 * actionChain
michael@0 1469 *
michael@0 1470 * @param object aRequest
michael@0 1471 * 'value' represents a nested array: inner array represents each event; outer array represents collection of events
michael@0 1472 */
michael@0 1473 actionChain: function MDA_actionChain(aRequest) {
michael@0 1474 this.command_id = this.getCommandId();
michael@0 1475 if (this.context == "chrome") {
michael@0 1476 this.sendError("Command 'actionChain' is not available in chrome context", 500, null, this.command_id);
michael@0 1477 }
michael@0 1478 else {
michael@0 1479 this.sendAsync("actionChain",
michael@0 1480 {
michael@0 1481 chain: aRequest.parameters.chain,
michael@0 1482 nextId: aRequest.parameters.nextId
michael@0 1483 },
michael@0 1484 this.command_id);
michael@0 1485 }
michael@0 1486 },
michael@0 1487
michael@0 1488 /**
michael@0 1489 * multiAction
michael@0 1490 *
michael@0 1491 * @param object aRequest
michael@0 1492 * 'value' represents a nested array: inner array represents each event;
michael@0 1493 * middle array represents collection of events for each finger
michael@0 1494 * outer array represents all the fingers
michael@0 1495 */
michael@0 1496
michael@0 1497 multiAction: function MDA_multiAction(aRequest) {
michael@0 1498 this.command_id = this.getCommandId();
michael@0 1499 if (this.context == "chrome") {
michael@0 1500 this.sendError("Command 'multiAction' is not available in chrome context", 500, null, this.command_id);
michael@0 1501 }
michael@0 1502 else {
michael@0 1503 this.sendAsync("multiAction",
michael@0 1504 {
michael@0 1505 value: aRequest.parameters.value,
michael@0 1506 maxlen: aRequest.parameters.max_length
michael@0 1507 },
michael@0 1508 this.command_id);
michael@0 1509 }
michael@0 1510 },
michael@0 1511
michael@0 1512 /**
michael@0 1513 * Find an element using the indicated search strategy.
michael@0 1514 *
michael@0 1515 * @param object aRequest
michael@0 1516 * 'using' member indicates which search method to use
michael@0 1517 * 'value' member is the value the client is looking for
michael@0 1518 */
michael@0 1519 findElement: function MDA_findElement(aRequest) {
michael@0 1520 let command_id = this.command_id = this.getCommandId();
michael@0 1521 if (this.context == "chrome") {
michael@0 1522 let id;
michael@0 1523 try {
michael@0 1524 let on_success = this.sendResponse.bind(this);
michael@0 1525 let on_error = this.sendError.bind(this);
michael@0 1526 id = this.curBrowser.elementManager.find(
michael@0 1527 this.getCurrentWindow(),
michael@0 1528 aRequest.parameters,
michael@0 1529 this.searchTimeout,
michael@0 1530 on_success,
michael@0 1531 on_error,
michael@0 1532 false,
michael@0 1533 command_id);
michael@0 1534 }
michael@0 1535 catch (e) {
michael@0 1536 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1537 return;
michael@0 1538 }
michael@0 1539 }
michael@0 1540 else {
michael@0 1541 this.sendAsync("findElementContent",
michael@0 1542 {
michael@0 1543 value: aRequest.parameters.value,
michael@0 1544 using: aRequest.parameters.using,
michael@0 1545 element: aRequest.parameters.element,
michael@0 1546 searchTimeout: this.searchTimeout
michael@0 1547 },
michael@0 1548 command_id);
michael@0 1549 }
michael@0 1550 },
michael@0 1551
michael@0 1552 /**
michael@0 1553 * Find elements using the indicated search strategy.
michael@0 1554 *
michael@0 1555 * @param object aRequest
michael@0 1556 * 'using' member indicates which search method to use
michael@0 1557 * 'value' member is the value the client is looking for
michael@0 1558 */
michael@0 1559 findElements: function MDA_findElements(aRequest) {
michael@0 1560 let command_id = this.command_id = this.getCommandId();
michael@0 1561 if (this.context == "chrome") {
michael@0 1562 let id;
michael@0 1563 try {
michael@0 1564 let on_success = this.sendResponse.bind(this);
michael@0 1565 let on_error = this.sendError.bind(this);
michael@0 1566 id = this.curBrowser.elementManager.find(this.getCurrentWindow(),
michael@0 1567 aRequest.parameters,
michael@0 1568 this.searchTimeout,
michael@0 1569 on_success,
michael@0 1570 on_error,
michael@0 1571 true,
michael@0 1572 command_id);
michael@0 1573 }
michael@0 1574 catch (e) {
michael@0 1575 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1576 return;
michael@0 1577 }
michael@0 1578 }
michael@0 1579 else {
michael@0 1580 this.sendAsync("findElementsContent",
michael@0 1581 {
michael@0 1582 value: aRequest.parameters.value,
michael@0 1583 using: aRequest.parameters.using,
michael@0 1584 element: aRequest.parameters.element,
michael@0 1585 searchTimeout: this.searchTimeout
michael@0 1586 },
michael@0 1587 command_id);
michael@0 1588 }
michael@0 1589 },
michael@0 1590
michael@0 1591 /**
michael@0 1592 * Return the active element on the page
michael@0 1593 */
michael@0 1594 getActiveElement: function MDA_getActiveElement(){
michael@0 1595 let command_id = this.command_id = this.getCommandId();
michael@0 1596 this.sendAsync("getActiveElement", {}, command_id);
michael@0 1597 },
michael@0 1598
michael@0 1599 /**
michael@0 1600 * Send click event to element
michael@0 1601 *
michael@0 1602 * @param object aRequest
michael@0 1603 * 'id' member holds the reference id to
michael@0 1604 * the element that will be clicked
michael@0 1605 */
michael@0 1606 clickElement: function MDA_clickElementent(aRequest) {
michael@0 1607 let command_id = this.command_id = this.getCommandId();
michael@0 1608 if (this.context == "chrome") {
michael@0 1609 try {
michael@0 1610 //NOTE: click atom fails, fall back to click() action
michael@0 1611 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1612 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1613 el.click();
michael@0 1614 this.sendOk(command_id);
michael@0 1615 }
michael@0 1616 catch (e) {
michael@0 1617 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1618 }
michael@0 1619 }
michael@0 1620 else {
michael@0 1621 // We need to protect against the click causing an OOP frame to close.
michael@0 1622 // This fires the mozbrowserclose event when it closes so we need to
michael@0 1623 // listen for it and then just send an error back. The person making the
michael@0 1624 // call should be aware something isnt right and handle accordingly
michael@0 1625 let curWindow = this.getCurrentWindow();
michael@0 1626 let self = this;
michael@0 1627 this.mozBrowserClose = function() {
michael@0 1628 curWindow.removeEventListener('mozbrowserclose', self.mozBrowserClose, true);
michael@0 1629 self.switchToGlobalMessageManager();
michael@0 1630 self.sendError("The frame closed during the click, recovering to allow further communications", 500, null, command_id);
michael@0 1631 };
michael@0 1632 curWindow.addEventListener('mozbrowserclose', this.mozBrowserClose, true);
michael@0 1633 this.sendAsync("clickElement",
michael@0 1634 { id: aRequest.parameters.id },
michael@0 1635 command_id);
michael@0 1636 }
michael@0 1637 },
michael@0 1638
michael@0 1639 /**
michael@0 1640 * Get a given attribute of an element
michael@0 1641 *
michael@0 1642 * @param object aRequest
michael@0 1643 * 'id' member holds the reference id to
michael@0 1644 * the element that will be inspected
michael@0 1645 * 'name' member holds the name of the attribute to retrieve
michael@0 1646 */
michael@0 1647 getElementAttribute: function MDA_getElementAttribute(aRequest) {
michael@0 1648 let command_id = this.command_id = this.getCommandId();
michael@0 1649 if (this.context == "chrome") {
michael@0 1650 try {
michael@0 1651 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1652 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1653 this.sendResponse(utils.getElementAttribute(el, aRequest.parameters.name),
michael@0 1654 command_id);
michael@0 1655 }
michael@0 1656 catch (e) {
michael@0 1657 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1658 }
michael@0 1659 }
michael@0 1660 else {
michael@0 1661 this.sendAsync("getElementAttribute",
michael@0 1662 {
michael@0 1663 id: aRequest.parameters.id,
michael@0 1664 name: aRequest.parameters.name
michael@0 1665 },
michael@0 1666 command_id);
michael@0 1667 }
michael@0 1668 },
michael@0 1669
michael@0 1670 /**
michael@0 1671 * Get the text of an element, if any. Includes the text of all child elements.
michael@0 1672 *
michael@0 1673 * @param object aRequest
michael@0 1674 * 'id' member holds the reference id to
michael@0 1675 * the element that will be inspected
michael@0 1676 */
michael@0 1677 getElementText: function MDA_getElementText(aRequest) {
michael@0 1678 let command_id = this.command_id = this.getCommandId();
michael@0 1679 if (this.context == "chrome") {
michael@0 1680 //Note: for chrome, we look at text nodes, and any node with a "label" field
michael@0 1681 try {
michael@0 1682 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1683 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1684 let lines = [];
michael@0 1685 this.getVisibleText(el, lines);
michael@0 1686 lines = lines.join("\n");
michael@0 1687 this.sendResponse(lines, command_id);
michael@0 1688 }
michael@0 1689 catch (e) {
michael@0 1690 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1691 }
michael@0 1692 }
michael@0 1693 else {
michael@0 1694 this.sendAsync("getElementText",
michael@0 1695 { id: aRequest.parameters.id },
michael@0 1696 command_id);
michael@0 1697 }
michael@0 1698 },
michael@0 1699
michael@0 1700 /**
michael@0 1701 * Get the tag name of the element.
michael@0 1702 *
michael@0 1703 * @param object aRequest
michael@0 1704 * 'id' member holds the reference id to
michael@0 1705 * the element that will be inspected
michael@0 1706 */
michael@0 1707 getElementTagName: function MDA_getElementTagName(aRequest) {
michael@0 1708 let command_id = this.command_id = this.getCommandId();
michael@0 1709 if (this.context == "chrome") {
michael@0 1710 try {
michael@0 1711 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1712 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1713 this.sendResponse(el.tagName.toLowerCase(), command_id);
michael@0 1714 }
michael@0 1715 catch (e) {
michael@0 1716 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1717 }
michael@0 1718 }
michael@0 1719 else {
michael@0 1720 this.sendAsync("getElementTagName",
michael@0 1721 { id: aRequest.parameters.id },
michael@0 1722 command_id);
michael@0 1723 }
michael@0 1724 },
michael@0 1725
michael@0 1726 /**
michael@0 1727 * Check if element is displayed
michael@0 1728 *
michael@0 1729 * @param object aRequest
michael@0 1730 * 'id' member holds the reference id to
michael@0 1731 * the element that will be checked
michael@0 1732 */
michael@0 1733 isElementDisplayed: function MDA_isElementDisplayed(aRequest) {
michael@0 1734 let command_id = this.command_id = this.getCommandId();
michael@0 1735 if (this.context == "chrome") {
michael@0 1736 try {
michael@0 1737 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1738 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1739 this.sendResponse(utils.isElementDisplayed(el), command_id);
michael@0 1740 }
michael@0 1741 catch (e) {
michael@0 1742 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1743 }
michael@0 1744 }
michael@0 1745 else {
michael@0 1746 this.sendAsync("isElementDisplayed",
michael@0 1747 { id:aRequest.parameters.id },
michael@0 1748 command_id);
michael@0 1749 }
michael@0 1750 },
michael@0 1751
michael@0 1752 /**
michael@0 1753 * Return the property of the computed style of an element
michael@0 1754 *
michael@0 1755 * @param object aRequest
michael@0 1756 * 'id' member holds the reference id to
michael@0 1757 * the element that will be checked
michael@0 1758 * 'propertyName' is the CSS rule that is being requested
michael@0 1759 */
michael@0 1760 getElementValueOfCssProperty: function MDA_getElementValueOfCssProperty(aRequest){
michael@0 1761 let command_id = this.command_id = this.getCommandId();
michael@0 1762 this.sendAsync("getElementValueOfCssProperty",
michael@0 1763 {id: aRequest.parameters.id, propertyName: aRequest.parameters.propertyName},
michael@0 1764 command_id);
michael@0 1765 },
michael@0 1766
michael@0 1767 /**
michael@0 1768 * Submit a form on a content page by either using form or element in a form
michael@0 1769 * @param object aRequest
michael@0 1770 * 'id' member holds the reference id to
michael@0 1771 * the element that will be checked
michael@0 1772 */
michael@0 1773 submitElement: function MDA_submitElement(aRequest) {
michael@0 1774 let command_id = this.command_id = this.getCommandId();
michael@0 1775 if (this.context == "chrome") {
michael@0 1776 this.sendError("Command 'submitElement' is not available in chrome context", 500, null, this.command_id);
michael@0 1777 }
michael@0 1778 else {
michael@0 1779 this.sendAsync("submitElement", {id: aRequest.parameters.id}, command_id);
michael@0 1780 }
michael@0 1781 },
michael@0 1782
michael@0 1783 /**
michael@0 1784 * Check if element is enabled
michael@0 1785 *
michael@0 1786 * @param object aRequest
michael@0 1787 * 'id' member holds the reference id to
michael@0 1788 * the element that will be checked
michael@0 1789 */
michael@0 1790 isElementEnabled: function MDA_isElementEnabled(aRequest) {
michael@0 1791 let command_id = this.command_id = this.getCommandId();
michael@0 1792 if (this.context == "chrome") {
michael@0 1793 try {
michael@0 1794 //Selenium atom doesn't quite work here
michael@0 1795 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1796 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1797 if (el.disabled != undefined) {
michael@0 1798 this.sendResponse(!!!el.disabled, command_id);
michael@0 1799 }
michael@0 1800 else {
michael@0 1801 this.sendResponse(true, command_id);
michael@0 1802 }
michael@0 1803 }
michael@0 1804 catch (e) {
michael@0 1805 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1806 }
michael@0 1807 }
michael@0 1808 else {
michael@0 1809 this.sendAsync("isElementEnabled",
michael@0 1810 { id:aRequest.parameters.id },
michael@0 1811 command_id);
michael@0 1812 }
michael@0 1813 },
michael@0 1814
michael@0 1815 /**
michael@0 1816 * Check if element is selected
michael@0 1817 *
michael@0 1818 * @param object aRequest
michael@0 1819 * 'id' member holds the reference id to
michael@0 1820 * the element that will be checked
michael@0 1821 */
michael@0 1822 isElementSelected: function MDA_isElementSelected(aRequest) {
michael@0 1823 let command_id = this.command_id = this.getCommandId();
michael@0 1824 if (this.context == "chrome") {
michael@0 1825 try {
michael@0 1826 //Selenium atom doesn't quite work here
michael@0 1827 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1828 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1829 if (el.checked != undefined) {
michael@0 1830 this.sendResponse(!!el.checked, command_id);
michael@0 1831 }
michael@0 1832 else if (el.selected != undefined) {
michael@0 1833 this.sendResponse(!!el.selected, command_id);
michael@0 1834 }
michael@0 1835 else {
michael@0 1836 this.sendResponse(true, command_id);
michael@0 1837 }
michael@0 1838 }
michael@0 1839 catch (e) {
michael@0 1840 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1841 }
michael@0 1842 }
michael@0 1843 else {
michael@0 1844 this.sendAsync("isElementSelected",
michael@0 1845 { id:aRequest.parameters.id },
michael@0 1846 command_id);
michael@0 1847 }
michael@0 1848 },
michael@0 1849
michael@0 1850 getElementSize: function MDA_getElementSize(aRequest) {
michael@0 1851 let command_id = this.command_id = this.getCommandId();
michael@0 1852 if (this.context == "chrome") {
michael@0 1853 try {
michael@0 1854 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1855 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1856 let clientRect = el.getBoundingClientRect();
michael@0 1857 this.sendResponse({width: clientRect.width, height: clientRect.height},
michael@0 1858 command_id);
michael@0 1859 }
michael@0 1860 catch (e) {
michael@0 1861 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1862 }
michael@0 1863 }
michael@0 1864 else {
michael@0 1865 this.sendAsync("getElementSize",
michael@0 1866 { id:aRequest.parameters.id },
michael@0 1867 command_id);
michael@0 1868 }
michael@0 1869 },
michael@0 1870
michael@0 1871 /**
michael@0 1872 * Send key presses to element after focusing on it
michael@0 1873 *
michael@0 1874 * @param object aRequest
michael@0 1875 * 'id' member holds the reference id to
michael@0 1876 * the element that will be checked
michael@0 1877 * 'value' member holds the value to send to the element
michael@0 1878 */
michael@0 1879 sendKeysToElement: function MDA_sendKeysToElement(aRequest) {
michael@0 1880 let command_id = this.command_id = this.getCommandId();
michael@0 1881 if (this.context == "chrome") {
michael@0 1882 try {
michael@0 1883 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1884 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1885 el.focus();
michael@0 1886 utils.sendString(aRequest.parameters.value.join(""), utils.window);
michael@0 1887 this.sendOk(command_id);
michael@0 1888 }
michael@0 1889 catch (e) {
michael@0 1890 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1891 }
michael@0 1892 }
michael@0 1893 else {
michael@0 1894 this.sendAsync("sendKeysToElement",
michael@0 1895 {
michael@0 1896 id:aRequest.parameters.id,
michael@0 1897 value: aRequest.parameters.value
michael@0 1898 },
michael@0 1899 command_id);
michael@0 1900 }
michael@0 1901 },
michael@0 1902
michael@0 1903 /**
michael@0 1904 * Sets the test name
michael@0 1905 *
michael@0 1906 * The test name is used in logging messages.
michael@0 1907 */
michael@0 1908 setTestName: function MDA_setTestName(aRequest) {
michael@0 1909 this.command_id = this.getCommandId();
michael@0 1910 this.logRequest("setTestName", aRequest);
michael@0 1911 this.testName = aRequest.parameters.value;
michael@0 1912 this.sendAsync("setTestName",
michael@0 1913 { value: aRequest.parameters.value },
michael@0 1914 this.command_id);
michael@0 1915 },
michael@0 1916
michael@0 1917 /**
michael@0 1918 * Clear the text of an element
michael@0 1919 *
michael@0 1920 * @param object aRequest
michael@0 1921 * 'id' member holds the reference id to
michael@0 1922 * the element that will be cleared
michael@0 1923 */
michael@0 1924 clearElement: function MDA_clearElement(aRequest) {
michael@0 1925 let command_id = this.command_id = this.getCommandId();
michael@0 1926 if (this.context == "chrome") {
michael@0 1927 //the selenium atom doesn't work here
michael@0 1928 try {
michael@0 1929 let el = this.curBrowser.elementManager.getKnownElement(
michael@0 1930 aRequest.parameters.id, this.getCurrentWindow());
michael@0 1931 if (el.nodeName == "textbox") {
michael@0 1932 el.value = "";
michael@0 1933 }
michael@0 1934 else if (el.nodeName == "checkbox") {
michael@0 1935 el.checked = false;
michael@0 1936 }
michael@0 1937 this.sendOk(command_id);
michael@0 1938 }
michael@0 1939 catch (e) {
michael@0 1940 this.sendError(e.message, e.code, e.stack, command_id);
michael@0 1941 }
michael@0 1942 }
michael@0 1943 else {
michael@0 1944 this.sendAsync("clearElement",
michael@0 1945 { id:aRequest.parameters.id },
michael@0 1946 command_id);
michael@0 1947 }
michael@0 1948 },
michael@0 1949
michael@0 1950 /**
michael@0 1951 * Get an element's location on the page.
michael@0 1952 *
michael@0 1953 * The returned point will contain the x and y coordinates of the
michael@0 1954 * top left-hand corner of the given element. The point (0,0)
michael@0 1955 * refers to the upper-left corner of the document.
michael@0 1956 *
michael@0 1957 * @return a point containing x and y coordinates as properties
michael@0 1958 */
michael@0 1959 getElementLocation: function MDA_getElementLocation(aRequest) {
michael@0 1960 this.command_id = this.getCommandId();
michael@0 1961 this.sendAsync("getElementLocation", {id: aRequest.parameters.id},
michael@0 1962 this.command_id);
michael@0 1963 },
michael@0 1964
michael@0 1965 /**
michael@0 1966 * Add a cookie to the document.
michael@0 1967 */
michael@0 1968 addCookie: function MDA_addCookie(aRequest) {
michael@0 1969 this.command_id = this.getCommandId();
michael@0 1970 this.sendAsync("addCookie",
michael@0 1971 { cookie:aRequest.parameters.cookie },
michael@0 1972 this.command_id);
michael@0 1973 },
michael@0 1974
michael@0 1975 /**
michael@0 1976 * Get all the cookies for the current domain.
michael@0 1977 *
michael@0 1978 * This is the equivalent of calling "document.cookie" and parsing
michael@0 1979 * the result.
michael@0 1980 */
michael@0 1981 getCookies: function MDA_getCookies() {
michael@0 1982 this.command_id = this.getCommandId();
michael@0 1983 this.sendAsync("getCookies", {}, this.command_id);
michael@0 1984 },
michael@0 1985
michael@0 1986 /**
michael@0 1987 * Delete all cookies that are visible to a document
michael@0 1988 */
michael@0 1989 deleteAllCookies: function MDA_deleteAllCookies() {
michael@0 1990 this.command_id = this.getCommandId();
michael@0 1991 this.sendAsync("deleteAllCookies", {}, this.command_id);
michael@0 1992 },
michael@0 1993
michael@0 1994 /**
michael@0 1995 * Delete a cookie by name
michael@0 1996 */
michael@0 1997 deleteCookie: function MDA_deleteCookie(aRequest) {
michael@0 1998 this.command_id = this.getCommandId();
michael@0 1999 this.sendAsync("deleteCookie",
michael@0 2000 { name:aRequest.parameters.name },
michael@0 2001 this.command_id);
michael@0 2002 },
michael@0 2003
michael@0 2004 /**
michael@0 2005 * Close the current window, ending the session if it's the last
michael@0 2006 * window currently open.
michael@0 2007 *
michael@0 2008 * On B2G this method is a noop and will return immediately.
michael@0 2009 */
michael@0 2010 close: function MDA_close() {
michael@0 2011 let command_id = this.command_id = this.getCommandId();
michael@0 2012 if (appName == "B2G") {
michael@0 2013 // We can't close windows so just return
michael@0 2014 this.sendOk(command_id);
michael@0 2015 }
michael@0 2016 else {
michael@0 2017 // Get the total number of windows
michael@0 2018 let numOpenWindows = 0;
michael@0 2019 let winEnum = this.getWinEnumerator();
michael@0 2020 while (winEnum.hasMoreElements()) {
michael@0 2021 numOpenWindows += 1;
michael@0 2022 winEnum.getNext();
michael@0 2023 }
michael@0 2024
michael@0 2025 // if there is only 1 window left, delete the session
michael@0 2026 if (numOpenWindows === 1) {
michael@0 2027 try {
michael@0 2028 this.sessionTearDown();
michael@0 2029 }
michael@0 2030 catch (e) {
michael@0 2031 this.sendError("Could not clear session", 500,
michael@0 2032 e.name + ": " + e.message, command_id);
michael@0 2033 return;
michael@0 2034 }
michael@0 2035 this.sendOk(command_id);
michael@0 2036 return;
michael@0 2037 }
michael@0 2038
michael@0 2039 try {
michael@0 2040 this.messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
michael@0 2041 this.getCurrentWindow().close();
michael@0 2042 this.sendOk(command_id);
michael@0 2043 }
michael@0 2044 catch (e) {
michael@0 2045 this.sendError("Could not close window: " + e.message, 13, e.stack,
michael@0 2046 command_id);
michael@0 2047 }
michael@0 2048 }
michael@0 2049 },
michael@0 2050
michael@0 2051 /**
michael@0 2052 * Deletes the session.
michael@0 2053 *
michael@0 2054 * If it is a desktop environment, it will close the session's tab and close all listeners
michael@0 2055 *
michael@0 2056 * If it is a B2G environment, it will make the main content listener sleep, and close
michael@0 2057 * all other listeners. The main content listener persists after disconnect (it's the homescreen),
michael@0 2058 * and can safely be reused.
michael@0 2059 */
michael@0 2060 sessionTearDown: function MDA_sessionTearDown() {
michael@0 2061 if (this.curBrowser != null) {
michael@0 2062 if (appName == "B2G") {
michael@0 2063 this.globalMessageManager.broadcastAsyncMessage(
michael@0 2064 "Marionette:sleepSession" + this.curBrowser.mainContentId, {});
michael@0 2065 this.curBrowser.knownFrames.splice(
michael@0 2066 this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1);
michael@0 2067 }
michael@0 2068 else {
michael@0 2069 //don't set this pref for B2G since the framescript can be safely reused
michael@0 2070 Services.prefs.setBoolPref("marionette.contentListener", false);
michael@0 2071 }
michael@0 2072 this.curBrowser.closeTab();
michael@0 2073 //delete session in each frame in each browser
michael@0 2074 for (let win in this.browsers) {
michael@0 2075 for (let i in this.browsers[win].knownFrames) {
michael@0 2076 this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
michael@0 2077 }
michael@0 2078 }
michael@0 2079 let winEnum = this.getWinEnumerator();
michael@0 2080 while (winEnum.hasMoreElements()) {
michael@0 2081 winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
michael@0 2082 }
michael@0 2083 this.curBrowser.frameManager.removeMessageManagerListeners(this.globalMessageManager);
michael@0 2084 }
michael@0 2085 this.switchToGlobalMessageManager();
michael@0 2086 // reset frame to the top-most frame
michael@0 2087 this.curFrame = null;
michael@0 2088 if (this.mainFrame) {
michael@0 2089 this.mainFrame.focus();
michael@0 2090 }
michael@0 2091 this.deleteFile('marionetteChromeScripts');
michael@0 2092 this.deleteFile('marionetteContentScripts');
michael@0 2093 },
michael@0 2094
michael@0 2095 /**
michael@0 2096 * Processes the 'deleteSession' request from the client by tearing down
michael@0 2097 * the session and responding 'ok'.
michael@0 2098 */
michael@0 2099 deleteSession: function MDA_deleteSession() {
michael@0 2100 let command_id = this.command_id = this.getCommandId();
michael@0 2101 try {
michael@0 2102 this.sessionTearDown();
michael@0 2103 }
michael@0 2104 catch (e) {
michael@0 2105 this.sendError("Could not delete session", 500, e.name + ": " + e.message, command_id);
michael@0 2106 return;
michael@0 2107 }
michael@0 2108 this.sendOk(command_id);
michael@0 2109 },
michael@0 2110
michael@0 2111 /**
michael@0 2112 * Returns the current status of the Application Cache
michael@0 2113 */
michael@0 2114 getAppCacheStatus: function MDA_getAppCacheStatus(aRequest) {
michael@0 2115 this.command_id = this.getCommandId();
michael@0 2116 this.sendAsync("getAppCacheStatus", {}, this.command_id);
michael@0 2117 },
michael@0 2118
michael@0 2119 _emu_cb_id: 0,
michael@0 2120 _emu_cbs: null,
michael@0 2121 runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
michael@0 2122 if (callback) {
michael@0 2123 if (!this._emu_cbs) {
michael@0 2124 this._emu_cbs = {};
michael@0 2125 }
michael@0 2126 this._emu_cbs[this._emu_cb_id] = callback;
michael@0 2127 }
michael@0 2128 this.sendToClient({emulator_cmd: cmd, id: this._emu_cb_id}, -1);
michael@0 2129 this._emu_cb_id += 1;
michael@0 2130 },
michael@0 2131
michael@0 2132 runEmulatorShell: function runEmulatorShell(args, callback) {
michael@0 2133 if (callback) {
michael@0 2134 if (!this._emu_cbs) {
michael@0 2135 this._emu_cbs = {};
michael@0 2136 }
michael@0 2137 this._emu_cbs[this._emu_cb_id] = callback;
michael@0 2138 }
michael@0 2139 this.sendToClient({emulator_shell: args, id: this._emu_cb_id}, -1);
michael@0 2140 this._emu_cb_id += 1;
michael@0 2141 },
michael@0 2142
michael@0 2143 emulatorCmdResult: function emulatorCmdResult(message) {
michael@0 2144 if (this.context != "chrome") {
michael@0 2145 this.sendAsync("emulatorCmdResult", message, -1);
michael@0 2146 return;
michael@0 2147 }
michael@0 2148
michael@0 2149 if (!this._emu_cbs) {
michael@0 2150 return;
michael@0 2151 }
michael@0 2152
michael@0 2153 let cb = this._emu_cbs[message.id];
michael@0 2154 delete this._emu_cbs[message.id];
michael@0 2155 if (!cb) {
michael@0 2156 return;
michael@0 2157 }
michael@0 2158 try {
michael@0 2159 cb(message.result);
michael@0 2160 }
michael@0 2161 catch(e) {
michael@0 2162 this.sendError(e.message, e.code, e.stack, -1);
michael@0 2163 return;
michael@0 2164 }
michael@0 2165 },
michael@0 2166
michael@0 2167 importScript: function MDA_importScript(aRequest) {
michael@0 2168 let command_id = this.command_id = this.getCommandId();
michael@0 2169 let converter =
michael@0 2170 Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
michael@0 2171 createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
michael@0 2172 converter.charset = "UTF-8";
michael@0 2173 let result = {};
michael@0 2174 let data = converter.convertToByteArray(aRequest.parameters.script, result);
michael@0 2175 let ch = Components.classes["@mozilla.org/security/hash;1"]
michael@0 2176 .createInstance(Components.interfaces.nsICryptoHash);
michael@0 2177 ch.init(ch.MD5);
michael@0 2178 ch.update(data, data.length);
michael@0 2179 let hash = ch.finish(true);
michael@0 2180 if (this.importedScriptHashes[this.context].indexOf(hash) > -1) {
michael@0 2181 //we have already imported this script
michael@0 2182 this.sendOk(command_id);
michael@0 2183 return;
michael@0 2184 }
michael@0 2185 this.importedScriptHashes[this.context].push(hash);
michael@0 2186 if (this.context == "chrome") {
michael@0 2187 let file;
michael@0 2188 if (this.importedScripts.exists()) {
michael@0 2189 file = FileUtils.openFileOutputStream(this.importedScripts,
michael@0 2190 FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY);
michael@0 2191 }
michael@0 2192 else {
michael@0 2193 //Note: The permission bits here don't actually get set (bug 804563)
michael@0 2194 this.importedScripts.createUnique(
michael@0 2195 Components.interfaces.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
michael@0 2196 file = FileUtils.openFileOutputStream(this.importedScripts,
michael@0 2197 FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
michael@0 2198 this.importedScripts.permissions = parseInt("0666", 8); //actually set permissions
michael@0 2199 }
michael@0 2200 file.write(aRequest.parameters.script, aRequest.parameters.script.length);
michael@0 2201 file.close();
michael@0 2202 this.sendOk(command_id);
michael@0 2203 }
michael@0 2204 else {
michael@0 2205 this.sendAsync("importScript",
michael@0 2206 { script: aRequest.parameters.script },
michael@0 2207 command_id);
michael@0 2208 }
michael@0 2209 },
michael@0 2210
michael@0 2211 clearImportedScripts: function MDA_clearImportedScripts(aRequest) {
michael@0 2212 let command_id = this.command_id = this.getCommandId();
michael@0 2213 try {
michael@0 2214 if (this.context == "chrome") {
michael@0 2215 this.deleteFile('marionetteChromeScripts');
michael@0 2216 }
michael@0 2217 else {
michael@0 2218 this.deleteFile('marionetteContentScripts');
michael@0 2219 }
michael@0 2220 }
michael@0 2221 catch (e) {
michael@0 2222 this.sendError("Could not clear imported scripts", 500, e.name + ": " + e.message, command_id);
michael@0 2223 return;
michael@0 2224 }
michael@0 2225 this.sendOk(command_id);
michael@0 2226 },
michael@0 2227
michael@0 2228 /**
michael@0 2229 * Takes a screenshot of a web element or the current frame.
michael@0 2230 *
michael@0 2231 * The screen capture is returned as a lossless PNG image encoded as
michael@0 2232 * a base 64 string. If the <code>id</code> argument is not null
michael@0 2233 * and refers to a present and visible web element's ID, the capture
michael@0 2234 * area will be limited to the bounding box of that element.
michael@0 2235 * Otherwise, the capture area will be the bounding box of the
michael@0 2236 * current frame.
michael@0 2237 *
michael@0 2238 * @param id an optional reference to a web element
michael@0 2239 * @param highlights an optional list of web elements to draw a red
michael@0 2240 * box around in the returned capture
michael@0 2241 * @return PNG image encoded as base 64 string
michael@0 2242 */
michael@0 2243 takeScreenshot: function MDA_takeScreenshot(aRequest) {
michael@0 2244 this.command_id = this.getCommandId();
michael@0 2245 this.sendAsync("takeScreenshot",
michael@0 2246 {id: aRequest.parameters.id,
michael@0 2247 highlights: aRequest.parameters.highlights},
michael@0 2248 this.command_id);
michael@0 2249 },
michael@0 2250
michael@0 2251 /**
michael@0 2252 * Get the current browser orientation.
michael@0 2253 *
michael@0 2254 * Will return one of the valid primary orientation values
michael@0 2255 * portrait-primary, landscape-primary, portrait-secondary, or
michael@0 2256 * landscape-secondary.
michael@0 2257 */
michael@0 2258 getScreenOrientation: function MDA_getScreenOrientation(aRequest) {
michael@0 2259 this.command_id = this.getCommandId();
michael@0 2260 let curWindow = this.getCurrentWindow();
michael@0 2261 let or = curWindow.screen.mozOrientation;
michael@0 2262 this.sendResponse(or, this.command_id);
michael@0 2263 },
michael@0 2264
michael@0 2265 /**
michael@0 2266 * Set the current browser orientation.
michael@0 2267 *
michael@0 2268 * The supplied orientation should be given as one of the valid
michael@0 2269 * orientation values. If the orientation is unknown, an error will
michael@0 2270 * be raised.
michael@0 2271 *
michael@0 2272 * Valid orientations are "portrait" and "landscape", which fall
michael@0 2273 * back to "portrait-primary" and "landscape-primary" respectively,
michael@0 2274 * and "portrait-secondary" as well as "landscape-secondary".
michael@0 2275 */
michael@0 2276 setScreenOrientation: function MDA_setScreenOrientation(aRequest) {
michael@0 2277 const ors = ["portrait", "landscape",
michael@0 2278 "portrait-primary", "landscape-primary",
michael@0 2279 "portrait-secondary", "landscape-secondary"];
michael@0 2280
michael@0 2281 this.command_id = this.getCommandId();
michael@0 2282 let or = String(aRequest.parameters.orientation);
michael@0 2283
michael@0 2284 let mozOr = or.toLowerCase();
michael@0 2285 if (ors.indexOf(mozOr) < 0) {
michael@0 2286 this.sendError("Unknown screen orientation: " + or, 500, null,
michael@0 2287 this.command_id);
michael@0 2288 return;
michael@0 2289 }
michael@0 2290
michael@0 2291 let curWindow = this.getCurrentWindow();
michael@0 2292 if (!curWindow.screen.mozLockOrientation(mozOr)) {
michael@0 2293 this.sendError("Unable to set screen orientation: " + or, 500,
michael@0 2294 null, this.command_id);
michael@0 2295 }
michael@0 2296 this.sendOk(this.command_id);
michael@0 2297 },
michael@0 2298
michael@0 2299 /**
michael@0 2300 * Helper function to convert an outerWindowID into a UID that Marionette
michael@0 2301 * tracks.
michael@0 2302 */
michael@0 2303 generateFrameId: function MDA_generateFrameId(id) {
michael@0 2304 let uid = id + (appName == "B2G" ? "-b2g" : "");
michael@0 2305 return uid;
michael@0 2306 },
michael@0 2307
michael@0 2308 /**
michael@0 2309 * Receives all messages from content messageManager
michael@0 2310 */
michael@0 2311 receiveMessage: function MDA_receiveMessage(message) {
michael@0 2312 // We need to just check if we need to remove the mozbrowserclose listener
michael@0 2313 if (this.mozBrowserClose !== null){
michael@0 2314 let curWindow = this.getCurrentWindow();
michael@0 2315 curWindow.removeEventListener('mozbrowserclose', this.mozBrowserClose, true);
michael@0 2316 this.mozBrowserClose = null;
michael@0 2317 }
michael@0 2318
michael@0 2319 switch (message.name) {
michael@0 2320 case "Marionette:done":
michael@0 2321 this.sendResponse(message.json.value, message.json.command_id);
michael@0 2322 break;
michael@0 2323 case "Marionette:ok":
michael@0 2324 this.sendOk(message.json.command_id);
michael@0 2325 break;
michael@0 2326 case "Marionette:error":
michael@0 2327 this.sendError(message.json.message, message.json.status, message.json.stacktrace, message.json.command_id);
michael@0 2328 break;
michael@0 2329 case "Marionette:log":
michael@0 2330 //log server-side messages
michael@0 2331 logger.info(message.json.message);
michael@0 2332 break;
michael@0 2333 case "Marionette:shareData":
michael@0 2334 //log messages from tests
michael@0 2335 if (message.json.log) {
michael@0 2336 this.marionetteLog.addLogs(message.json.log);
michael@0 2337 }
michael@0 2338 break;
michael@0 2339 case "Marionette:runEmulatorCmd":
michael@0 2340 case "Marionette:runEmulatorShell":
michael@0 2341 this.sendToClient(message.json, -1);
michael@0 2342 break;
michael@0 2343 case "Marionette:switchToFrame":
michael@0 2344 this.curBrowser.frameManager.switchToFrame(message);
michael@0 2345 this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get();
michael@0 2346 break;
michael@0 2347 case "Marionette:switchToModalOrigin":
michael@0 2348 this.curBrowser.frameManager.switchToModalOrigin(message);
michael@0 2349 this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager.get();
michael@0 2350 break;
michael@0 2351 case "Marionette:switchedToFrame":
michael@0 2352 logger.info("Switched to frame: " + JSON.stringify(message.json));
michael@0 2353 if (message.json.restorePrevious) {
michael@0 2354 this.currentFrameElement = this.previousFrameElement;
michael@0 2355 }
michael@0 2356 else {
michael@0 2357 if (message.json.storePrevious) {
michael@0 2358 // we don't arbitrarily save previousFrameElement, since
michael@0 2359 // we allow frame switching after modals appear, which would
michael@0 2360 // override this value and we'd lose our reference
michael@0 2361 this.previousFrameElement = this.currentFrameElement;
michael@0 2362 }
michael@0 2363 this.currentFrameElement = message.json.frameValue;
michael@0 2364 }
michael@0 2365 break;
michael@0 2366 case "Marionette:register":
michael@0 2367 // This code processes the content listener's registration information
michael@0 2368 // and either accepts the listener, or ignores it
michael@0 2369 let nullPrevious = (this.curBrowser.curFrameId == null);
michael@0 2370 let listenerWindow =
michael@0 2371 Services.wm.getOuterWindowWithId(message.json.value);
michael@0 2372
michael@0 2373 //go in here if we're already in a remote frame.
michael@0 2374 if ((!listenerWindow || (listenerWindow.location &&
michael@0 2375 listenerWindow.location.href != message.json.href)) &&
michael@0 2376 (this.curBrowser.frameManager.currentRemoteFrame !== null)) {
michael@0 2377 // The outerWindowID from an OOP frame will not be meaningful to
michael@0 2378 // the parent process here, since each process maintains its own
michael@0 2379 // independent window list. So, it will either be null (!listenerWindow)
michael@0 2380 // if we're already in a remote frame,
michael@0 2381 // or it will point to some random window, which will hopefully
michael@0 2382 // cause an href mismatch. Currently this only happens
michael@0 2383 // in B2G for OOP frames registered in Marionette:switchToFrame, so
michael@0 2384 // we'll acknowledge the switchToFrame message here.
michael@0 2385 // XXX: Should have a better way of determining that this message
michael@0 2386 // is from a remote frame.
michael@0 2387 this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value);
michael@0 2388 this.sendOk(this.command_id);
michael@0 2389 }
michael@0 2390
michael@0 2391 let browserType;
michael@0 2392 try {
michael@0 2393 browserType = message.target.getAttribute("type");
michael@0 2394 } catch (ex) {
michael@0 2395 // browserType remains undefined.
michael@0 2396 }
michael@0 2397 let reg = {};
michael@0 2398 // this will be sent to tell the content process if it is the main content
michael@0 2399 let mainContent = (this.curBrowser.mainContentId == null);
michael@0 2400 if (!browserType || browserType != "content") {
michael@0 2401 //curBrowser holds all the registered frames in knownFrames
michael@0 2402 reg.id = this.curBrowser.register(this.generateFrameId(message.json.value),
michael@0 2403 listenerWindow);
michael@0 2404 }
michael@0 2405 // set to true if we updated mainContentId
michael@0 2406 mainContent = ((mainContent == true) && (this.curBrowser.mainContentId != null));
michael@0 2407 if (mainContent) {
michael@0 2408 this.mainContentFrameId = this.curBrowser.curFrameId;
michael@0 2409 }
michael@0 2410 this.curBrowser.elementManager.seenItems[reg.id] = Cu.getWeakReference(listenerWindow);
michael@0 2411 if (nullPrevious && (this.curBrowser.curFrameId != null)) {
michael@0 2412 if (!this.sendAsync("newSession",
michael@0 2413 { B2G: (appName == "B2G") },
michael@0 2414 this.newSessionCommandId)) {
michael@0 2415 return;
michael@0 2416 }
michael@0 2417 if (this.curBrowser.newSession) {
michael@0 2418 this.getSessionCapabilities();
michael@0 2419 this.newSessionCommandId = null;
michael@0 2420 }
michael@0 2421 }
michael@0 2422 return [reg, mainContent];
michael@0 2423 case "Marionette:emitTouchEvent":
michael@0 2424 let globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
michael@0 2425 .getService(Ci.nsIMessageBroadcaster);
michael@0 2426 globalMessageManager.broadcastAsyncMessage(
michael@0 2427 "MarionetteMainListener:emitTouchEvent", message.json);
michael@0 2428 return;
michael@0 2429 }
michael@0 2430 }
michael@0 2431 };
michael@0 2432
michael@0 2433 MarionetteServerConnection.prototype.requestTypes = {
michael@0 2434 "getMarionetteID": MarionetteServerConnection.prototype.getMarionetteID,
michael@0 2435 "sayHello": MarionetteServerConnection.prototype.sayHello,
michael@0 2436 "newSession": MarionetteServerConnection.prototype.newSession,
michael@0 2437 "getSessionCapabilities": MarionetteServerConnection.prototype.getSessionCapabilities,
michael@0 2438 "log": MarionetteServerConnection.prototype.log,
michael@0 2439 "getLogs": MarionetteServerConnection.prototype.getLogs,
michael@0 2440 "setContext": MarionetteServerConnection.prototype.setContext,
michael@0 2441 "executeScript": MarionetteServerConnection.prototype.execute,
michael@0 2442 "setScriptTimeout": MarionetteServerConnection.prototype.setScriptTimeout,
michael@0 2443 "timeouts": MarionetteServerConnection.prototype.timeouts,
michael@0 2444 "singleTap": MarionetteServerConnection.prototype.singleTap,
michael@0 2445 "actionChain": MarionetteServerConnection.prototype.actionChain,
michael@0 2446 "multiAction": MarionetteServerConnection.prototype.multiAction,
michael@0 2447 "executeAsyncScript": MarionetteServerConnection.prototype.executeWithCallback,
michael@0 2448 "executeJSScript": MarionetteServerConnection.prototype.executeJSScript,
michael@0 2449 "setSearchTimeout": MarionetteServerConnection.prototype.setSearchTimeout,
michael@0 2450 "findElement": MarionetteServerConnection.prototype.findElement,
michael@0 2451 "findElements": MarionetteServerConnection.prototype.findElements,
michael@0 2452 "clickElement": MarionetteServerConnection.prototype.clickElement,
michael@0 2453 "getElementAttribute": MarionetteServerConnection.prototype.getElementAttribute,
michael@0 2454 "getElementText": MarionetteServerConnection.prototype.getElementText,
michael@0 2455 "getElementTagName": MarionetteServerConnection.prototype.getElementTagName,
michael@0 2456 "isElementDisplayed": MarionetteServerConnection.prototype.isElementDisplayed,
michael@0 2457 "getElementValueOfCssProperty": MarionetteServerConnection.prototype.getElementValueOfCssProperty,
michael@0 2458 "submitElement": MarionetteServerConnection.prototype.submitElement,
michael@0 2459 "getElementSize": MarionetteServerConnection.prototype.getElementSize,
michael@0 2460 "isElementEnabled": MarionetteServerConnection.prototype.isElementEnabled,
michael@0 2461 "isElementSelected": MarionetteServerConnection.prototype.isElementSelected,
michael@0 2462 "sendKeysToElement": MarionetteServerConnection.prototype.sendKeysToElement,
michael@0 2463 "getElementLocation": MarionetteServerConnection.prototype.getElementLocation,
michael@0 2464 "getElementPosition": MarionetteServerConnection.prototype.getElementLocation, // deprecated
michael@0 2465 "clearElement": MarionetteServerConnection.prototype.clearElement,
michael@0 2466 "getTitle": MarionetteServerConnection.prototype.getTitle,
michael@0 2467 "getWindowType": MarionetteServerConnection.prototype.getWindowType,
michael@0 2468 "getPageSource": MarionetteServerConnection.prototype.getPageSource,
michael@0 2469 "get": MarionetteServerConnection.prototype.get,
michael@0 2470 "goUrl": MarionetteServerConnection.prototype.get, // deprecated
michael@0 2471 "getCurrentUrl": MarionetteServerConnection.prototype.getCurrentUrl,
michael@0 2472 "getUrl": MarionetteServerConnection.prototype.getCurrentUrl, // deprecated
michael@0 2473 "goBack": MarionetteServerConnection.prototype.goBack,
michael@0 2474 "goForward": MarionetteServerConnection.prototype.goForward,
michael@0 2475 "refresh": MarionetteServerConnection.prototype.refresh,
michael@0 2476 "getWindowHandle": MarionetteServerConnection.prototype.getWindowHandle,
michael@0 2477 "getCurrentWindowHandle": MarionetteServerConnection.prototype.getWindowHandle, // Selenium 2 compat
michael@0 2478 "getWindow": MarionetteServerConnection.prototype.getWindowHandle, // deprecated
michael@0 2479 "getWindowHandles": MarionetteServerConnection.prototype.getWindowHandles,
michael@0 2480 "getCurrentWindowHandles": MarionetteServerConnection.prototype.getWindowHandles, // Selenium 2 compat
michael@0 2481 "getWindows": MarionetteServerConnection.prototype.getWindowHandles, // deprecated
michael@0 2482 "getActiveFrame": MarionetteServerConnection.prototype.getActiveFrame,
michael@0 2483 "switchToFrame": MarionetteServerConnection.prototype.switchToFrame,
michael@0 2484 "switchToWindow": MarionetteServerConnection.prototype.switchToWindow,
michael@0 2485 "deleteSession": MarionetteServerConnection.prototype.deleteSession,
michael@0 2486 "emulatorCmdResult": MarionetteServerConnection.prototype.emulatorCmdResult,
michael@0 2487 "importScript": MarionetteServerConnection.prototype.importScript,
michael@0 2488 "clearImportedScripts": MarionetteServerConnection.prototype.clearImportedScripts,
michael@0 2489 "getAppCacheStatus": MarionetteServerConnection.prototype.getAppCacheStatus,
michael@0 2490 "close": MarionetteServerConnection.prototype.close,
michael@0 2491 "closeWindow": MarionetteServerConnection.prototype.close, // deprecated
michael@0 2492 "setTestName": MarionetteServerConnection.prototype.setTestName,
michael@0 2493 "takeScreenshot": MarionetteServerConnection.prototype.takeScreenshot,
michael@0 2494 "screenShot": MarionetteServerConnection.prototype.takeScreenshot, // deprecated
michael@0 2495 "screenshot": MarionetteServerConnection.prototype.takeScreenshot, // Selenium 2 compat
michael@0 2496 "addCookie": MarionetteServerConnection.prototype.addCookie,
michael@0 2497 "getCookies": MarionetteServerConnection.prototype.getCookies,
michael@0 2498 "getAllCookies": MarionetteServerConnection.prototype.getCookies, // deprecated
michael@0 2499 "deleteAllCookies": MarionetteServerConnection.prototype.deleteAllCookies,
michael@0 2500 "deleteCookie": MarionetteServerConnection.prototype.deleteCookie,
michael@0 2501 "getActiveElement": MarionetteServerConnection.prototype.getActiveElement,
michael@0 2502 "getScreenOrientation": MarionetteServerConnection.prototype.getScreenOrientation,
michael@0 2503 "setScreenOrientation": MarionetteServerConnection.prototype.setScreenOrientation
michael@0 2504 };
michael@0 2505
michael@0 2506 /**
michael@0 2507 * Creates a BrowserObj. BrowserObjs handle interactions with the
michael@0 2508 * browser, according to the current environment (desktop, b2g, etc.)
michael@0 2509 *
michael@0 2510 * @param nsIDOMWindow win
michael@0 2511 * The window whose browser needs to be accessed
michael@0 2512 */
michael@0 2513
michael@0 2514 function BrowserObj(win, server) {
michael@0 2515 this.DESKTOP = "desktop";
michael@0 2516 this.B2G = "B2G";
michael@0 2517 this.browser;
michael@0 2518 this.tab = null; //Holds a reference to the created tab, if any
michael@0 2519 this.window = win;
michael@0 2520 this.knownFrames = [];
michael@0 2521 this.curFrameId = null;
michael@0 2522 this.startPage = "about:blank";
michael@0 2523 this.mainContentId = null; // used in B2G to identify the homescreen content page
michael@0 2524 this.newSession = true; //used to set curFrameId upon new session
michael@0 2525 this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
michael@0 2526 this.setBrowser(win);
michael@0 2527 this.frameManager = new FrameManager(server); //We should have one FM per BO so that we can handle modals in each Browser
michael@0 2528
michael@0 2529 //register all message listeners
michael@0 2530 this.frameManager.addMessageManagerListeners(server.messageManager);
michael@0 2531 }
michael@0 2532
michael@0 2533 BrowserObj.prototype = {
michael@0 2534 /**
michael@0 2535 * Set the browser if the application is not B2G
michael@0 2536 *
michael@0 2537 * @param nsIDOMWindow win
michael@0 2538 * current window reference
michael@0 2539 */
michael@0 2540 setBrowser: function BO_setBrowser(win) {
michael@0 2541 switch (appName) {
michael@0 2542 case "Firefox":
michael@0 2543 this.browser = win.gBrowser;
michael@0 2544 break;
michael@0 2545 case "Fennec":
michael@0 2546 this.browser = win.BrowserApp;
michael@0 2547 break;
michael@0 2548 }
michael@0 2549 },
michael@0 2550 /**
michael@0 2551 * Called when we start a session with this browser.
michael@0 2552 *
michael@0 2553 * In a desktop environment, if newTab is true, it will start
michael@0 2554 * a new 'about:blank' tab and change focus to this tab.
michael@0 2555 *
michael@0 2556 * This will also set the active messagemanager for this object
michael@0 2557 *
michael@0 2558 * @param boolean newTab
michael@0 2559 * If true, create new tab
michael@0 2560 */
michael@0 2561 startSession: function BO_startSession(newTab, win, callback) {
michael@0 2562 if (appName != "Firefox") {
michael@0 2563 callback(win, newTab);
michael@0 2564 }
michael@0 2565 else if (newTab) {
michael@0 2566 this.tab = this.addTab(this.startPage);
michael@0 2567 //if we have a new tab, make it the selected tab
michael@0 2568 this.browser.selectedTab = this.tab;
michael@0 2569 let newTabBrowser = this.browser.getBrowserForTab(this.tab);
michael@0 2570 // wait for tab to be loaded
michael@0 2571 newTabBrowser.addEventListener("load", function onLoad() {
michael@0 2572 newTabBrowser.removeEventListener("load", onLoad, true);
michael@0 2573 callback(win, newTab);
michael@0 2574 }, true);
michael@0 2575 }
michael@0 2576 else {
michael@0 2577 //set this.tab to the currently focused tab
michael@0 2578 if (this.browser != undefined && this.browser.selectedTab != undefined) {
michael@0 2579 this.tab = this.browser.selectedTab;
michael@0 2580 }
michael@0 2581 callback(win, newTab);
michael@0 2582 }
michael@0 2583 },
michael@0 2584
michael@0 2585 /**
michael@0 2586 * Closes current tab
michael@0 2587 */
michael@0 2588 closeTab: function BO_closeTab() {
michael@0 2589 if (this.tab != null && (appName != "B2G")) {
michael@0 2590 this.browser.removeTab(this.tab);
michael@0 2591 this.tab = null;
michael@0 2592 }
michael@0 2593 },
michael@0 2594
michael@0 2595 /**
michael@0 2596 * Opens a tab with given uri
michael@0 2597 *
michael@0 2598 * @param string uri
michael@0 2599 * URI to open
michael@0 2600 */
michael@0 2601 addTab: function BO_addTab(uri) {
michael@0 2602 return this.browser.addTab(uri, true);
michael@0 2603 },
michael@0 2604
michael@0 2605 /**
michael@0 2606 * Loads content listeners if we don't already have them
michael@0 2607 *
michael@0 2608 * @param string script
michael@0 2609 * path of script to load
michael@0 2610 * @param nsIDOMWindow frame
michael@0 2611 * frame to load the script in
michael@0 2612 */
michael@0 2613 loadFrameScript: function BO_loadFrameScript(script, frame) {
michael@0 2614 frame.window.messageManager.loadFrameScript(script, true, true);
michael@0 2615 Services.prefs.setBoolPref("marionette.contentListener", true);
michael@0 2616 },
michael@0 2617
michael@0 2618 /**
michael@0 2619 * Registers a new frame, and sets its current frame id to this frame
michael@0 2620 * if it is not already assigned, and if a) we already have a session
michael@0 2621 * or b) we're starting a new session and it is the right start frame.
michael@0 2622 *
michael@0 2623 * @param string uid
michael@0 2624 * frame uid
michael@0 2625 * @param object frameWindow
michael@0 2626 * the DOMWindow object of the frame that's being registered
michael@0 2627 */
michael@0 2628 register: function BO_register(uid, frameWindow) {
michael@0 2629 if (this.curFrameId == null) {
michael@0 2630 // If we're setting up a new session on Firefox, we only process the
michael@0 2631 // registration for this frame if it belongs to the tab we've just
michael@0 2632 // created.
michael@0 2633 if ((!this.newSession) ||
michael@0 2634 (this.newSession &&
michael@0 2635 ((appName != "Firefox") ||
michael@0 2636 frameWindow == this.browser.getBrowserForTab(this.tab).contentWindow))) {
michael@0 2637 this.curFrameId = uid;
michael@0 2638 this.mainContentId = uid;
michael@0 2639 }
michael@0 2640 }
michael@0 2641 this.knownFrames.push(uid); //used to delete sessions
michael@0 2642 return uid;
michael@0 2643 },
michael@0 2644 }
michael@0 2645
michael@0 2646 /**
michael@0 2647 * Marionette server -- this class holds a reference to a socket and creates
michael@0 2648 * MarionetteServerConnection objects as needed.
michael@0 2649 */
michael@0 2650 this.MarionetteServer = function MarionetteServer(port, forceLocal) {
michael@0 2651 let flags = Ci.nsIServerSocket.KeepWhenOffline;
michael@0 2652 if (forceLocal) {
michael@0 2653 flags |= Ci.nsIServerSocket.LoopbackOnly;
michael@0 2654 }
michael@0 2655 let socket = new ServerSocket(port, flags, 0);
michael@0 2656 logger.info("Listening on port " + socket.port + "\n");
michael@0 2657 socket.asyncListen(this);
michael@0 2658 this.listener = socket;
michael@0 2659 this.nextConnId = 0;
michael@0 2660 this.connections = {};
michael@0 2661 };
michael@0 2662
michael@0 2663 MarionetteServer.prototype = {
michael@0 2664 onSocketAccepted: function(serverSocket, clientSocket)
michael@0 2665 {
michael@0 2666 logger.debug("accepted connection on " + clientSocket.host + ":" + clientSocket.port);
michael@0 2667
michael@0 2668 let input = clientSocket.openInputStream(0, 0, 0);
michael@0 2669 let output = clientSocket.openOutputStream(0, 0, 0);
michael@0 2670 let aTransport = new DebuggerTransport(input, output);
michael@0 2671 let connID = "conn" + this.nextConnID++ + '.';
michael@0 2672 let conn = new MarionetteServerConnection(connID, aTransport, this);
michael@0 2673 this.connections[connID] = conn;
michael@0 2674
michael@0 2675 // Create a root actor for the connection and send the hello packet.
michael@0 2676 conn.sayHello();
michael@0 2677 aTransport.ready();
michael@0 2678 },
michael@0 2679
michael@0 2680 closeListener: function() {
michael@0 2681 this.listener.close();
michael@0 2682 this.listener = null;
michael@0 2683 },
michael@0 2684
michael@0 2685 _connectionClosed: function DS_connectionClosed(aConnection) {
michael@0 2686 delete this.connections[aConnection.prefix];
michael@0 2687 }
michael@0 2688 };

mercurial