Wed, 31 Dec 2014 06:55:50 +0100
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 | }; |