testing/marionette/marionette-server.js

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

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

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial