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