Bug 841126: Update DataChannels to use new stack option to ignore port numbers over...
[gecko.git] / testing / marionette / marionette-actors.js
blobf1be88058b2b3bfb48b2fd8dcfad3e664987f628
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 /**
8  * Gecko-specific actors.
9  */
11 const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
13 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
15 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
16                .getService(Ci.mozIJSSubScriptLoader);
17 loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
18 loader.loadSubScript("chrome://marionette/content/marionette-log-obj.js");
19 loader.loadSubScript("chrome://marionette/content/marionette-perf.js");
20 Cu.import("chrome://marionette/content/marionette-elements.js");
21 let utils = {};
22 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
23 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
24 loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
26 let specialpowers = {};
27 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
28                      specialpowers);
29 specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver();
30 specialpowers.specialPowersObserver.init();
32 Cu.import("resource://gre/modules/Services.jsm");
33 Cu.import("resource://gre/modules/FileUtils.jsm");
34 Cu.import("resource://gre/modules/NetUtil.jsm");  
36 Services.prefs.setBoolPref("marionette.contentListener", false);
37 let appName = Services.appinfo.name;
39 // import logger
40 Cu.import("resource://gre/modules/services-common/log4moz.js");
41 let logger = Log4Moz.repository.getLogger("Marionette");
42 logger.info('marionette-actors.js loaded');
44 Services.prefs.setBoolPref("network.gonk.manage-offline-status", false);
45 Services.io.manageOfflineStatus = false;
46 Services.io.offline = false;
48 // This is used to prevent newSession from returning before the telephony
49 // API's are ready; see bug 792647.  This assumes that marionette-actors.js
50 // will be loaded before the 'system-message-listener-ready' message
51 // is fired.  If this stops being true, this approach will have to change.
52 let systemMessageListenerReady = false;
53 Services.obs.addObserver(function() {
54   systemMessageListenerReady = true;
55 }, "system-message-listener-ready", false);
57 /**
58  * Creates the root actor once a connection is established
59  */
61 function createRootActor(aConnection)
63   return new MarionetteRootActor(aConnection);
66 /**
67  * Root actor for Marionette. Add any future actors to its actor pool.
68  * Implements methods needed by resource:///modules/devtools/dbg-server.jsm
69  */
71 function MarionetteRootActor(aConnection)
73   this.conn = aConnection;
74   this._marionetteActor = new MarionetteDriverActor(this.conn);
75   this._marionetteActorPool = null; //hold future actors
77   this._marionetteActorPool = new ActorPool(this.conn);
78   this._marionetteActorPool.addActor(this._marionetteActor);
79   this.conn.addActorPool(this._marionetteActorPool);
82 MarionetteRootActor.prototype = {
83   /**
84    * Called when a client first makes a connection
85    *
86    * @return object
87    *         returns the name of the actor, the application type, and any traits
88    */
89   sayHello: function MRA_sayHello() {
90     return { from: "root",
91              applicationType: "gecko",
92              traits: [] };
93   },
95   /**
96    * Called when client disconnects. Cleans up marionette driver actions.
97    */
98   disconnect: function MRA_disconnect() {
99     this._marionetteActor.deleteSession();
100   },
102   /**
103    * Used to get the running marionette actor, so we can issue commands
104    *
105    * @return object
106    *         Returns the ID the client can use to communicate with the
107    *         MarionetteDriverActor
108    */
109   getMarionetteID: function MRA_getMarionette() {
110     return { "from": "root",
111              "id": this._marionetteActor.actorID } ;
112   }
115 // register the calls
116 MarionetteRootActor.prototype.requestTypes = {
117   "getMarionetteID": MarionetteRootActor.prototype.getMarionetteID,
118   "sayHello": MarionetteRootActor.prototype.sayHello
122  * An object representing a frame that Marionette has loaded a
123  * frame script in.
124  */
125 function MarionetteRemoteFrame(windowId, frameId) {
126   this.windowId = windowId;
127   this.frameId = frameId;
128   this.targetFrameId = null;
129   this.messageManager = null;
130   this.command_id = null;
132 // persistent list of remote frames that Marionette has loaded a frame script in
133 let remoteFrames = [];
136  * This actor is responsible for all marionette API calls. It gets created
137  * for each connection and manages all chrome and browser based calls. It
138  * mediates content calls by issuing appropriate messages to the content process.
139  */
140 function MarionetteDriverActor(aConnection)
142   this.uuidGen = Cc["@mozilla.org/uuid-generator;1"]
143                    .getService(Ci.nsIUUIDGenerator);
145   this.conn = aConnection;
146   this.globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
147                              .getService(Ci.nsIMessageBroadcaster);
148   this.messageManager = this.globalMessageManager;
149   this.browsers = {}; //holds list of BrowserObjs
150   this.curBrowser = null; // points to current browser
151   this.context = "content";
152   this.scriptTimeout = null;
153   this.pageTimeout = null;
154   this.timer = null;
155   this.marionetteLog = new MarionetteLogObj();
156   this.marionettePerf = new MarionettePerfData();
157   this.command_id = null;
158   this.mainFrame = null; //topmost chrome frame
159   this.curFrame = null; //subframe that currently has focus
160   this.importedScripts = FileUtils.getFile('TmpD', ['marionettescriptchrome']);
161   this.currentRemoteFrame = null; // a member of remoteFrames
162   this.testName = null;
163   this.mozBrowserClose = null;
165   //register all message listeners
166   this.addMessageManagerListeners(this.messageManager);
169 MarionetteDriverActor.prototype = {
171   //name of the actor
172   actorPrefix: "marionette",
174   /**
175    * Helper methods:
176    */
178   /**
179    * Switches to the global ChromeMessageBroadcaster, potentially replacing a frame-specific
180    * ChromeMessageSender.  Has no effect if the global ChromeMessageBroadcaster is already
181    * in use.  If this replaces a frame-specific ChromeMessageSender, it removes the message
182    * listeners from that sender, and then puts the corresponding frame script "to sleep",
183    * which removes most of the message listeners from it as well.
184    */
185   switchToGlobalMessageManager: function MDA_switchToGlobalMM() {
186     if (this.currentRemoteFrame !== null) {
187       this.removeMessageManagerListeners(this.messageManager);
188       try {
189         // this can fail if the frame is already gone
190         this.sendAsync("sleepSession");
191       }
192       catch(e) {}
193     }
194     this.messageManager = this.globalMessageManager;
195     this.currentRemoteFrame = null;
196   },
198   /**
199    * Helper method to send async messages to the content listener
200    *
201    * @param string name
202    *        Suffix of the targetted message listener (Marionette:<suffix>)
203    * @param object values
204    *        Object to send to the listener
205    */
206   sendAsync: function MDA_sendAsync(name, values) {
207     if (this.currentRemoteFrame !== null) {
208       this.messageManager.sendAsyncMessage(
209         "Marionette:" + name + this.currentRemoteFrame.targetFrameId, values);
210     }
211     else {
212       this.messageManager.broadcastAsyncMessage(
213         "Marionette:" + name + this.curBrowser.curFrameId, values);
214     }
215   },
217   /**
218    * Adds listeners for messages from content frame scripts.
219    *
220    * @param object messageManager
221    *        The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
222    *        to which the listeners should be added.
223    */
224   addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) {
225     messageManager.addMessageListener("Marionette:ok", this);
226     messageManager.addMessageListener("Marionette:done", this);
227     messageManager.addMessageListener("Marionette:error", this);
228     messageManager.addMessageListener("Marionette:log", this);
229     messageManager.addMessageListener("Marionette:shareData", this);
230     messageManager.addMessageListener("Marionette:register", this);
231     messageManager.addMessageListener("Marionette:runEmulatorCmd", this);
232     messageManager.addMessageListener("Marionette:switchToFrame", this);
233   },
235   /**
236    * Removes listeners for messages from content frame scripts.
237    *
238    * @param object messageManager
239    *        The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
240    *        from which the listeners should be removed.
241    */
242   removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) {
243     messageManager.removeMessageListener("Marionette:ok", this);
244     messageManager.removeMessageListener("Marionette:done", this);
245     messageManager.removeMessageListener("Marionette:error", this);
246     messageManager.removeMessageListener("Marionette:log", this);
247     messageManager.removeMessageListener("Marionette:shareData", this);
248     messageManager.removeMessageListener("Marionette:register", this);
249     messageManager.removeMessageListener("Marionette:runEmulatorCmd", this);
250     messageManager.removeMessageListener("Marionette:switchToFrame", this);
251   },
253   logRequest: function MDA_logRequest(type, data) {
254     logger.debug("Got request: " + type + ", data: " + JSON.stringify(data) + ", id: " + this.command_id);
255   },
257   /**
258    * Generic method to pass a response to the client
259    *
260    * @param object msg
261    *        Response to send back to client
262    * @param string command_id
263    *        Unique identifier assigned to the client's request.
264    *        Used to distinguish the asynchronous responses.
265    */
266   sendToClient: function MDA_sendToClient(msg, command_id) {
267     logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id +
268                 ", " + this.command_id);
269     if (!command_id) {
270       logger.warn("got a response with no command_id");
271       return;
272     }
273     else if (command_id != -1) {
274       // A command_id of -1 is used for emulator callbacks, and those
275       // don't use this.command_id.
276       if (!this.command_id) {
277         // A null value for this.command_id means we've already processed
278         // a message for the previous value, and so the current message is a
279         // duplicate.
280         logger.warn("ignoring duplicate response for command_id " + command_id);
281         return;
282       }
283       else if (this.command_id != command_id) {
284         logger.warn("ignoring out-of-sync response");
285         return;
286       }
287     }
288     this.conn.send(msg);
289     if (command_id != -1) {
290       // Don't unset this.command_id if this message is to process an
291       // emulator callback, since another response for this command_id is
292       // expected, after the containing call to execute_async_script finishes.
293       this.command_id = null;
294     }
295   },
297   /**
298    * Send a value to client
299    *
300    * @param object value
301    *        Value to send back to client
302    * @param string command_id
303    *        Unique identifier assigned to the client's request.
304    *        Used to distinguish the asynchronous responses.
305    */
306   sendResponse: function MDA_sendResponse(value, command_id) {
307     if (typeof(value) == 'undefined')
308         value = null;
309     this.sendToClient({from:this.actorID, value: value}, command_id);
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);
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] = 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    * Marionette API:
474    *
475    * All methods implementing a command from the client should create a
476    * command_id, and then use this command_id in all messages exchanged with
477    * the frame scripts and with responses sent to the client.  This prevents
478    * commands and responses from getting out-of-sync, which can happen in
479    * the case of execute_async calls that timeout and then later send a
480    * response, and other situations.  See bug 779011. See setScriptTimeout()
481    * for a basic example.
482    */
484   /**
485    * Create a new session. This creates a BrowserObj.
486    *
487    * In a desktop environment, this opens a new 'about:blank' tab for 
488    * the client to test in.
489    *
490    */
491   newSession: function MDA_newSession() {
492     this.command_id = this.getCommandId();
493     this.newSessionCommandId = this.command_id;
495     this.scriptTimeout = 10000;
497     function waitForWindow() {
498       let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
499       let win = this.getCurrentWindow();
500       if (!win ||
501           (appName == "Firefox" && !win.gBrowser) ||
502           (appName == "Fennec" && !win.BrowserApp)) { 
503         checkTimer.initWithCallback(waitForWindow.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
504       }
505       else {
506         this.startBrowser(win, true);
507       }
508     }
510     this.switchToGlobalMessageManager();
512     if (!Services.prefs.getBoolPref("marionette.contentListener")) {
513       waitForWindow.call(this);
514     }
515     else if ((appName != "Firefox") && (this.curBrowser == null)) {
516       //if there is a content listener, then we just wake it up
517       this.addBrowser(this.getCurrentWindow());
518       this.curBrowser.startSession(false, this.getCurrentWindow(), this.whenBrowserStarted);
519       this.messageManager.broadcastAsyncMessage("Marionette:restart", {});
520     }
521     else {
522       this.sendError("Session already running", 500, null, this.command_id);
523     }
524   },
526   getSessionCapabilities: function MDA_getSessionCapabilities(){
527     this.command_id = this.getCommandId();
529     let rotatable = appName == "B2G" ? true : false;
531     let value = {
532           'appBuildId' : Services.appinfo.appBuildID,
533           'XULappId' : Services.appinfo.ID,
534           'cssSelectorsEnabled': true,
535           'browserName': appName,
536           'handlesAlerts': false,
537           'javascriptEnabled': true,
538           'nativeEvents': false,
539           'platform': Services.appinfo.OS,
540           'rotatable': rotatable,
541           'takesScreenshot': false,
542           'version': Services.appinfo.version
543     };
545     this.sendResponse(value, this.command_id);
546   },
548   getStatus: function MDA_getStatus(){
549     this.command_id = this.getCommandId();
551     let arch;
552     try {
553       arch = (Services.appinfo.XPCOMABI || 'unknown').split('-')[0]
554     }
555     catch (ignored) {
556       arch = 'unknown'
557     };
559     let value = {
560           'os': {
561             'arch': arch,
562             'name': Services.appinfo.OS,
563             'version': 'unknown'
564           },
565           'build': {
566             'revision': 'unknown',
567             'time': Services.appinfo.platformBuildID,
568             'version': Services.appinfo.version
569           }
570     };
572     this.sendResponse(value, this.command_id);
573   },
575   /**
576    * Log message. Accepts user defined log-level.
577    *
578    * @param object aRequest
579    *        'value' member holds log message
580    *        'level' member hold log level
581    */
582   log: function MDA_log(aRequest) {
583     this.command_id = this.getCommandId();
584     this.marionetteLog.log(aRequest.value, aRequest.level);
585     this.sendOk(this.command_id);
586   },
588   /**
589    * Return all logged messages.
590    */
591   getLogs: function MDA_getLogs() {
592     this.command_id = this.getCommandId();
593     this.sendResponse(this.marionetteLog.getLogs(), this.command_id);
594   },
595   
596   /**
597    * Log some performance data
598    */
599   addPerfData: function MDA_addPerfData(aRequest) {
600     this.command_id = this.getCommandId();
601     this.marionettePerf.addPerfData(aRequest.suite, aRequest.name, aRequest.value);
602     this.sendOk(this.command_id);
603   },
605   /**
606    * Retrieve the performance data
607    */
608   getPerfData: function MDA_getPerfData() {
609     this.command_id = this.getCommandId();
610     this.sendResponse(this.marionettePerf.getPerfData(), this.command_id);
611   },
613   /**
614    * Sets the context of the subsequent commands to be either 'chrome' or 'content'
615    *
616    * @param object aRequest
617    *        'value' member holds the name of the context to be switched to
618    */
619   setContext: function MDA_setContext(aRequest) {
620     this.command_id = this.getCommandId();
621     this.logRequest("setContext", aRequest);
622     let context = aRequest.value;
623     if (context != "content" && context != "chrome") {
624       this.sendError("invalid context", 500, null, this.command_id);
625     }
626     else {
627       this.context = context;
628       this.sendOk(this.command_id);
629     }
630   },
632   /**
633    * Returns a chrome sandbox that can be used by the execute_foo functions.
634    *
635    * @param nsIDOMWindow aWindow
636    *        Window in which we will execute code
637    * @param Marionette marionette
638    *        Marionette test instance
639    * @param object args
640    *        Client given args
641    * @return Sandbox
642    *        Returns the sandbox
643    */
644   createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args, specialPowers, command_id) {
645     try {
646       args = this.curBrowser.elementManager.convertWrappedArguments(args, aWindow);
647     }
648     catch(e) {
649       this.sendError(e.message, e.code, e.stack, command_id);
650       return;
651     }
653     let _chromeSandbox = new Cu.Sandbox(aWindow,
654        { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''});
655     _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
656     _chromeSandbox.__marionetteParams = args;
657     _chromeSandbox.testUtils = utils;
659     marionette.exports.forEach(function(fn) {
660       try {
661         _chromeSandbox[fn] = marionette[fn].bind(marionette);
662       }
663       catch(e) {
664         _chromeSandbox[fn] = marionette[fn];
665       }
666     });
668     _chromeSandbox.isSystemMessageListenerReady =
669         function() { return systemMessageListenerReady; }
671     if (specialPowers == true) {
672       loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js",
673                            _chromeSandbox);
674       loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserverAPI.js",
675                            _chromeSandbox);
676       loader.loadSubScript("chrome://specialpowers/content/ChromePowers.js",
677                            _chromeSandbox);
678     }
680     return _chromeSandbox;
681   },
683   /**
684    * Executes a script in the given sandbox.
685    *
686    * @param Sandbox sandbox
687    *        Sandbox in which the script will run
688    * @param string script
689    *        The script to run
690    * @param boolean directInject
691    *        If true, then the script will be run as is,
692    *        and not as a function body (as you would
693    *        do using the WebDriver spec)
694    * @param boolean async
695    *        True if the script is asynchronous
696    */
697   executeScriptInSandbox: function MDA_executeScriptInSandbox(sandbox, script,
698      directInject, async, command_id, timeout) {
700     if (directInject && async &&
701         (timeout == null || timeout == 0)) {
702       this.sendError("Please set a timeout", 21, null, command_id);
703       return;
704     }
706     if (this.importedScripts.exists()) {
707       let stream = Cc["@mozilla.org/network/file-input-stream;1"].  
708                     createInstance(Ci.nsIFileInputStream);
709       stream.init(this.importedScripts, -1, 0, 0);
710       let data = NetUtil.readInputStreamToString(stream, stream.available());
711       script = data + script;
712     }
714     let res = Cu.evalInSandbox(script, sandbox, "1.8");
716     if (directInject && !async &&
717         (res == undefined || res.passed == undefined)) {
718       this.sendError("finish() not called", 500, null, command_id);
719       return;
720     }
722     if (!async) {
723       this.sendResponse(this.curBrowser.elementManager.wrapValue(res),
724                         command_id);
725     }
726   },
728   /**
729    * Execute the given script either as a function body (executeScript)
730    * or directly (for 'mochitest' like JS Marionette tests)
731    *
732    * @param object aRequest
733    *        'value' member is the script to run
734    *        'args' member holds the arguments to the script
735    * @param boolean directInject
736    *        if true, it will be run directly and not as a 
737    *        function body
738    */
739   execute: function MDA_execute(aRequest, directInject) {
740     let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
741     let command_id = this.command_id = this.getCommandId();
742     this.logRequest("execute", aRequest);
743     if (aRequest.newSandbox == undefined) {
744       //if client does not send a value in newSandbox, 
745       //then they expect the same behaviour as webdriver
746       aRequest.newSandbox = true;
747     }
748     if (this.context == "content") {
749       this.sendAsync("executeScript", {value: aRequest.value,
750                                        args: aRequest.args,
751                                        newSandbox: aRequest.newSandbox,
752                                        timeout: timeout,
753                                        command_id: command_id,
754                                        specialPowers: aRequest.specialPowers});
755       return;
756     }
758     let curWindow = this.getCurrentWindow();
759     let marionette = new Marionette(this, curWindow, "chrome",
760                                     this.marionetteLog, this.marionettePerf,
761                                     timeout, this.testName);
762     let _chromeSandbox = this.createExecuteSandbox(curWindow,
763                                                    marionette,
764                                                    aRequest.args,
765                                                    aRequest.specialPowers,
766                                                    command_id);
767     if (!_chromeSandbox)
768       return;
770     try {
771       _chromeSandbox.finish = function chromeSandbox_finish() {
772         return marionette.generate_results();
773       };
775       let script;
776       if (directInject) {
777         script = aRequest.value;
778       }
779       else {
780         script = "let func = function() {" +
781                        aRequest.value + 
782                      "};" +
783                      "func.apply(null, __marionetteParams);";
784       }
785       this.executeScriptInSandbox(_chromeSandbox, script, directInject,
786                                   false, command_id, timeout);
787     }
788     catch (e) {
789       this.sendError(e.name + ': ' + e.message, 17, e.stack, command_id);
790     }
791   },
793   /**
794    * Set the timeout for asynchronous script execution
795    *
796    * @param object aRequest
797    *        'value' member is time in milliseconds to set timeout
798    */
799   setScriptTimeout: function MDA_setScriptTimeout(aRequest) {
800     this.command_id = this.getCommandId();
801     let timeout = parseInt(aRequest.value);
802     if(isNaN(timeout)){
803       this.sendError("Not a Number", 500, null, this.command_id);
804     }
805     else {
806       this.scriptTimeout = timeout;
807       this.sendOk(this.command_id);
808     }
809   },
811   /**
812    * execute pure JS script. Used to execute 'mochitest'-style Marionette tests.
813    *
814    * @param object aRequest
815    *        'value' member holds the script to execute
816    *        'args' member holds the arguments to the script
817    *        'timeout' member will be used as the script timeout if it is given
818    */
819   executeJSScript: function MDA_executeJSScript(aRequest) {
820     let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
821     let command_id = this.command_id = this.getCommandId();
822     //all pure JS scripts will need to call Marionette.finish() to complete the test.
823     if (aRequest.newSandbox == undefined) {
824       //if client does not send a value in newSandbox, 
825       //then they expect the same behaviour as webdriver
826       aRequest.newSandbox = true;
827     }
828     if (this.context == "chrome") {
829       if (aRequest.async) {
830         this.executeWithCallback(aRequest, aRequest.async);
831       }
832       else {
833         this.execute(aRequest, true);
834       }
835     }
836     else {
837       this.sendAsync("executeJSScript", { value: aRequest.value,
838                                           args: aRequest.args,
839                                           newSandbox: aRequest.newSandbox,
840                                           async: aRequest.async,
841                                           timeout: timeout,
842                                           command_id: command_id,
843                                           specialPowers: aRequest.specialPowers });
844    }
845   },
847   /**
848    * This function is used by executeAsync and executeJSScript to execute a script
849    * in a sandbox. 
850    * 
851    * For executeJSScript, it will return a message only when the finish() method is called.
852    * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] 
853    * method is called, or if it times out.
854    *
855    * @param object aRequest
856    *        'value' member holds the script to execute
857    *        'args' member holds the arguments for the script
858    * @param boolean directInject
859    *        if true, it will be run directly and not as a 
860    *        function body
861    */
862   executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
863     let timeout = aRequest.scriptTimeout ? aRequest.scriptTimeout : this.scriptTimeout;
864     let command_id = this.command_id = this.getCommandId();
865     this.logRequest("executeWithCallback", aRequest);
866     if (aRequest.newSandbox == undefined) {
867       //if client does not send a value in newSandbox, 
868       //then they expect the same behaviour as webdriver
869       aRequest.newSandbox = true;
870     }
872     if (this.context == "content") {
873       this.sendAsync("executeAsyncScript", {value: aRequest.value,
874                                             args: aRequest.args,
875                                             id: this.command_id,
876                                             newSandbox: aRequest.newSandbox,
877                                             timeout: timeout,
878                                             command_id: command_id,
879                                             specialPowers: aRequest.specialPowers});
880       return;
881     }
883     let curWindow = this.getCurrentWindow();
884     let original_onerror = curWindow.onerror;
885     let that = this;
886     that.timeout = timeout;
887     let marionette = new Marionette(this, curWindow, "chrome",
888                                     this.marionetteLog, this.marionettePerf,
889                                     timeout, this.testName);
890     marionette.command_id = this.command_id;
892     function chromeAsyncReturnFunc(value, status) {
893       if (that._emu_cbs && Object.keys(that._emu_cbs).length) {
894         value = "Emulator callback still pending when finish() called";
895         status = 500;
896         that._emu_cbs = null;
897       }
899       if (value == undefined)
900         value = null;
901       if (that.command_id == marionette.command_id) {
902         if (that.timer != null) {
903           that.timer.cancel();
904           that.timer = null;
905         }
907         curWindow.onerror = original_onerror;
909         if (status == 0 || status == undefined) {
910           that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status},
911                             marionette.command_id);
912         }
913         else {
914           let error_msg = {message: value, status: status, stacktrace: null};
915           that.sendToClient({from: that.actorID, error: error_msg},
916                             marionette.command_id);
917         }
918       }
919     }
921     curWindow.onerror = function (errorMsg, url, lineNumber) {
922       chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17);
923       return true;
924     };
926     function chromeAsyncFinish() {
927       chromeAsyncReturnFunc(marionette.generate_results(), 0);
928     }
930     let _chromeSandbox = this.createExecuteSandbox(curWindow,
931                                                    marionette,
932                                                    aRequest.args,
933                                                    aRequest.specialPowers,
934                                                    command_id);
935     if (!_chromeSandbox)
936       return;
938     try {
940       this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
941       if (this.timer != null) {
942         this.timer.initWithCallback(function() {
943           chromeAsyncReturnFunc("timed out", 28);
944         }, that.timeout, Ci.nsITimer.TYPE_ONESHOT);
945       }
947       _chromeSandbox.returnFunc = chromeAsyncReturnFunc;
948       _chromeSandbox.finish = chromeAsyncFinish;
950       let script;
951       if (directInject) {
952         script = aRequest.value;
953       }
954       else {
955         script =  '__marionetteParams.push(returnFunc);'
956                 + 'let marionetteScriptFinished = returnFunc;'
957                 + 'let __marionetteFunc = function() {' + aRequest.value + '};'
958                 + '__marionetteFunc.apply(null, __marionetteParams);';
959       }
961       this.executeScriptInSandbox(_chromeSandbox, script, directInject,
962                                   true, command_id, timeout);
963     } catch (e) {
964       chromeAsyncReturnFunc(e.name + ": " + e.message, 17);
965     }
966   },
968   /**
969    * Navigates to given url
970    *
971    * @param object aRequest
972    *        'value' member holds the url to navigate to
973    */
974   goUrl: function MDA_goUrl(aRequest) {
975     let command_id = this.command_id = this.getCommandId();
976     if (this.context != "chrome") {
977       aRequest.command_id = command_id;
978       aRequest.pageTimeout = this.pageTimeout;
979       this.sendAsync("goUrl", aRequest);
980       return;
981     }
983     this.getCurrentWindow().location.href = aRequest.value;
984     let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
985     let start = new Date().getTime();
986     let end = null;
987     function checkLoad() { 
988       end = new Date().getTime();
989       let elapse = end - start;
990       if (this.pageTimeout == null || elapse <= this.pageTimeout){
991         if (curWindow.document.readyState == "complete") { 
992           sendOk(command_id);
993           return;
994         }
995         else{ 
996           checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
997         }
998       }
999       else{
1000         sendError("Error loading page", 13, null, command_id);
1001         return;
1002       }
1003     }//end
1004     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
1005   },
1007   /**
1008    * Gets current url
1009    */
1010   getUrl: function MDA_getUrl() {
1011     this.command_id = this.getCommandId();
1012     if (this.context == "chrome") {
1013       this.sendResponse(this.getCurrentWindow().location.href, this.command_id);
1014     }
1015     else {
1016       this.sendAsync("getUrl", {command_id: this.command_id});
1017     }
1018   },
1020   /**
1021    * Gets the current title of the window
1022    */
1023   getTitle: function MDA_getTitle() {
1024     this.command_id = this.getCommandId();
1025     this.sendAsync("getTitle", {command_id: this.command_id});
1026   },
1028   /**
1029    * Gets the page source of the content document
1030    */
1031   getPageSource: function MDA_getPageSource(){
1032     this.command_id = this.getCommandId();
1033     if (this.context == "chrome"){
1034       var curWindow = this.getCurrentWindow();
1035       var XMLSerializer = curWindow.XMLSerializer; 
1036       var pageSource = new XMLSerializer().serializeToString(curWindow.document);
1037       this.sendResponse(pageSource, this.command_id);
1038     }
1039     else {
1040       this.sendAsync("getPageSource", {command_id: this.command_id});
1041     }
1042   },
1044   /**
1045    * Go back in history
1046    */
1047   goBack: function MDA_goBack() {
1048     this.command_id = this.getCommandId();
1049     this.sendAsync("goBack", {command_id: this.command_id});
1050   },
1052   /**
1053    * Go forward in history
1054    */
1055   goForward: function MDA_goForward() {
1056     this.command_id = this.getCommandId();
1057     this.sendAsync("goForward", {command_id: this.command_id});
1058   },
1060   /**
1061    * Refresh the page
1062    */
1063   refresh: function MDA_refresh() {
1064     this.command_id = this.getCommandId();
1065     this.sendAsync("refresh", {command_id: this.command_id});
1066   },
1068   /**
1069    * Get the current window's server-assigned ID
1070    */
1071   getWindow: function MDA_getWindow() {
1072     this.command_id = this.getCommandId();
1073     for (let i in this.browsers) {
1074       if (this.curBrowser == this.browsers[i]) {
1075         this.sendResponse(i, this.command_id);
1076       }
1077     }
1078   },
1080   /**
1081    * Get the server-assigned IDs of all available windows
1082    */
1083   getWindows: function MDA_getWindows() {
1084     this.command_id = this.getCommandId();
1085     let res = [];
1086     let winEn = this.getWinEnumerator(); 
1087     while(winEn.hasMoreElements()) {
1088       let foundWin = winEn.getNext();
1089       let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
1090       winId = winId + ((appName == "B2G") ? '-b2g' : '');
1091       res.push(winId)
1092     }
1093     this.sendResponse(res, this.command_id);
1094   },
1096   /**
1097    * Switch to a window based on name or server-assigned id.
1098    * Searches based on name, then id.
1099    *
1100    * @param object aRequest
1101    *        'value' member holds the name or id of the window to switch to
1102    */
1103   switchToWindow: function MDA_switchToWindow(aRequest) {
1104     let command_id = this.command_id = this.getCommandId();
1105     let winEn = this.getWinEnumerator(); 
1106     while(winEn.hasMoreElements()) {
1107       let foundWin = winEn.getNext();
1108       let winId = foundWin.QueryInterface(Ci.nsIInterfaceRequestor)
1109                           .getInterface(Ci.nsIDOMWindowUtils)
1110                           .outerWindowID;
1111       winId = winId + ((appName == "B2G") ? '-b2g' : '');
1112       if (aRequest.value == foundWin.name || aRequest.value == winId) {
1113         if (this.browsers[winId] == undefined) {
1114           //enable Marionette in that browser window
1115           this.startBrowser(foundWin, false);
1116         }
1117         else {
1118           utils.window = foundWin;
1119           this.curBrowser = this.browsers[winId];
1120         }
1121         this.sendOk(command_id);
1122         return;
1123       }
1124     }
1125     this.sendError("Unable to locate window " + aRequest.value, 23, null,
1126                    command_id);
1127   },
1129   /**
1130    * Switch to a given frame within the current window
1131    *
1132    * @param object aRequest
1133    *        'element' is the element to switch to
1134    *        'value' if element is not set, then this
1135    *                holds either the id, name or index 
1136    *                of the frame to switch to
1137    */
1138   switchToFrame: function MDA_switchToFrame(aRequest) {
1139     let command_id = this.command_id = this.getCommandId();
1140     this.logRequest("switchToFrame", aRequest);
1141     let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
1142     let checkLoad = function() { 
1143       let errorRegex = /about:.+(error)|(blocked)\?/;
1144       if (curWindow.document.readyState == "complete") { 
1145         this.sendOk(command_id);
1146         return;
1147       } 
1148       else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)) {
1149         this.sendError("Error loading page", 13, null, command_id);
1150         return;
1151       }
1152       
1153       checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1154     }
1155     let curWindow = this.getCurrentWindow();
1156     if (this.context == "chrome") {
1157       let foundFrame = null;
1158       if ((aRequest.value == null) && (aRequest.element == null)) {
1159         this.curFrame = null;
1160         if (aRequest.focus) {
1161           this.mainFrame.focus();
1162         }
1163         checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1164         return;
1165       }
1166       if (aRequest.element != undefined) {
1167         if (this.curBrowser.elementManager.seenItems[aRequest.element] != undefined) {
1168           let wantedFrame = this.curBrowser.elementManager.getKnownElement(aRequest.element, curWindow); //HTMLIFrameElement
1169           let numFrames = curWindow.frames.length;
1170           for (let i = 0; i < numFrames; i++) {
1171             if (curWindow.frames[i].frameElement == wantedFrame) {
1172               curWindow = curWindow.frames[i]; 
1173               this.curFrame = curWindow;
1174               if (aRequest.focus) {
1175                 this.curFrame.focus();
1176               }
1177               checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1178               return;
1179           }
1180         }
1181       }
1182     }
1183     switch(typeof(aRequest.value)) {
1184       case "string" :
1185         let foundById = null;
1186         let numFrames = curWindow.frames.length;
1187         for (let i = 0; i < numFrames; i++) {
1188           //give precedence to name
1189           let frame = curWindow.frames[i];
1190           let frameElement = frame.frameElement;
1191           if (frame.name == aRequest.value) {
1192             foundFrame = i;
1193             break;
1194           } else if ((foundById == null) && (frameElement.id == aRequest.value)) {
1195             foundById = i;
1196           }
1197         }
1198         if ((foundFrame == null) && (foundById != null)) {
1199           foundFrame = foundById;
1200         }
1201         break;
1202       case "number":
1203         if (curWindow.frames[aRequest.value] != undefined) {
1204           foundFrame = aRequest.value;
1205         }
1206         break;
1207       }
1208       if (foundFrame != null) {
1209         curWindow = curWindow.frames[foundFrame];
1210         this.curFrame = curWindow;
1211         if (aRequest.focus) {
1212           this.curFrame.focus();
1213         }
1214         checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
1215       } else {
1216         this.sendError("Unable to locate frame: " + aRequest.value, 8, null,
1217                        command_id);
1218       }
1219     }
1220     else {
1221       if ((!aRequest.value) && (!aRequest.element) &&
1222           (this.currentRemoteFrame !== null)) {
1223         // We're currently using a ChromeMessageSender for a remote frame, so this
1224         // request indicates we need to switch back to the top-level (parent) frame.
1225         // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so
1226         // we send the message to the right listener.
1227         this.switchToGlobalMessageManager();
1228       }
1229       aRequest.command_id = command_id;
1230       this.sendAsync("switchToFrame", aRequest);
1231     }
1232   },
1234   /**
1235    * Set timeout for searching for elements
1236    *
1237    * @param object aRequest
1238    *        'value' holds the search timeout in milliseconds
1239    */
1240   setSearchTimeout: function MDA_setSearchTimeout(aRequest) {
1241     this.command_id = this.getCommandId();
1242     if (this.context == "chrome") {
1243       try {
1244         this.curBrowser.elementManager.setSearchTimeout(aRequest.value);
1245         this.sendOk(this.command_id);
1246       }
1247       catch (e) {
1248         this.sendError(e.message, e.code, e.stack, this.command_id);
1249       }
1250     }
1251     else {
1252       this.sendAsync("setSearchTimeout", {value: aRequest.value,
1253                                           command_id: this.command_id});
1254     }
1255   },
1257   /**
1258    * Set timeout for page loading, searching and scripts
1259    *
1260    * @param object aRequest
1261    *        'type' hold the type of timeout
1262    *        'ms' holds the timeout in milliseconds
1263    */
1264   timeouts: function MDA_timeouts(aRequest){
1265     /*setTimeout*/
1266     this.command_id = this.getCommandId();
1267     let timeout_type = aRequest.timeoutType;
1268     let timeout = parseInt(aRequest.ms);
1269     if (isNaN(timeout)) {
1270       this.sendError("Not a Number", 500, null, this.command_id);
1271     }
1272     else {
1273       if (timeout_type == "implicit") {
1274         aRequest.value = aRequest.ms;
1275         this.setSearchTimeout(aRequest);
1276       }
1277       else if (timeout_type == "script") {
1278         aRequest.value = aRequest.ms;
1279         this.setScriptTimeout(aRequest);
1280       }
1281       else {
1282         this.pageTimeout = timeout;
1283         this.sendOk(this.command_id);
1284       }
1285     }
1286   },
1288   /**
1289    * Single Tap
1290    *
1291    * @param object aRequest
1292             'element' represents the ID of the element to single tap on
1293    */
1294   singleTap: function MDA_singleTap(aRequest) {
1295     this.command_id = this.getCommandId();
1296     let serId = aRequest.element;
1297     let x = aRequest.x;
1298     let y = aRequest.y;
1299     if (this.context == "chrome") {
1300       this.sendError("Not in Chrome", 500, null, this.command_id);
1301     }
1302     else {
1303       this.sendAsync("singleTap", {value: serId,
1304                                    corx: x,
1305                                    cory: y,
1306                                    command_id: this.command_id});
1307     }
1308   },
1310   /**
1311    * Double Tap
1312    *
1313    * @param object aRequest
1314    *        'element' represents the ID of the element to double tap on
1315    */
1316   doubleTap: function MDA_doubleTap(aRequest) {
1317     this.command_id = this.getCommandId();
1318     let serId = aRequest.element;
1319     let x = aRequest.x;
1320     let y = aRequest.y;
1321     if (this.context == "chrome") {
1322       this.sendError("Not in Chrome", 500, null, this.command_id);
1323     }
1324     else {
1325       this.sendAsync("doubleTap", {value: serId,
1326                                    corx: x,
1327                                    cory: y,
1328                                    command_id: this.command_id});
1329     }
1330   },
1332   /**
1333    * Start touch
1334    *
1335    * @param object aRequest
1336    *        'element' represents the ID of the element to touch
1337    */
1338   press: function MDA_press(aRequest) {
1339     this.command_id = this.getCommandId();
1340     let element = aRequest.element;
1341     let x = aRequest.x;
1342     let y = aRequest.y;
1343     if (this.context == "chrome") {
1344       this.sendError("Not in Chrome", 500, null, this.command_id);
1345     }
1346     else {
1347       this.sendAsync("press", {value: element,
1348                                corx: x,
1349                                cory: y,
1350                                command_id: this.command_id});
1351     }
1352   },
1354   /**
1355    * Cancel touch
1356    *
1357    * @param object aRequest
1358    *        'element' represents the ID of the element to touch
1359    */
1360   cancelTouch: function MDA_cancelTouch(aRequest) {
1361     this.command_id = this.getCommandId();
1362     let element = aRequest.element;
1363     let touchId = aRequest.touchId;
1364     if (this.context == "chrome") {
1365       this.sendError("Not in Chrome", 500, null, this.command_id);
1366     }
1367     else {
1368       this.sendAsync("cancelTouch", {value: element,
1369                                      touchId: touchId,
1370                                      command_id: this.command_id});
1371     }
1372   },
1374   /**
1375    * End touch
1376    *
1377    * @param object aRequest
1378    *        'element' represents the ID of the element to end the touch
1379    */
1380   release: function MDA_release(aRequest) {
1381     this.command_id = this.getCommandId();
1382     let element = aRequest.element;
1383     let touchId = aRequest.touchId;
1384     let x = aRequest.x;
1385     let y = aRequest.y;
1386     if (this.context == "chrome") {
1387       this.sendError("Not in Chrome", 500, null, this.command_id);
1388     }
1389     else {
1390       this.sendAsync("release", {value: element,
1391                                  touchId: touchId,
1392                                  corx: x,
1393                                  cory: y,
1394                                  command_id: this.command_id});
1395     }
1396   },
1398   /**
1399    * actionChain
1400    *
1401    * @param object aRequest
1402    *        'value' represents a nested array: inner array represents each event; outer array represents collection of events
1403    */
1404   actionChain: function MDA_actionChain(aRequest) {
1405     this.command_id = this.getCommandId();
1406     if (this.context == "chrome") {
1407       this.sendError("Not in Chrome", 500, null, this.command_id);
1408     }
1409     else {
1410       this.sendAsync("actionChain", {chain: aRequest.chain,
1411                                      nextId: aRequest.nextId,
1412                                      command_id: this.command_id});
1413     }
1414   },
1416   /**
1417    * multiAction
1418    *
1419    * @param object aRequest
1420    *        'value' represents a nested array: inner array represents each event;
1421    *        middle array represents collection of events for each finger
1422    *        outer array represents all the fingers
1423    */
1425   multiAction: function MDA_multiAction(aRequest) {
1426     this.command_id = this.getCommandId();
1427     if (this.context == "chrome") {
1428        this.sendError("Not in Chrome", 500, null, this.command_id);
1429     }
1430     else {
1431       this.sendAsync("multiAction", {value: aRequest.value,
1432                                      maxlen: aRequest.max_length,
1433                                      command_id: this.command_id});
1434    }
1435  },
1437   /**
1438    * Find an element using the indicated search strategy.
1439    *
1440    * @param object aRequest
1441    *        'using' member indicates which search method to use
1442    *        'value' member is the value the client is looking for
1443    */
1444   findElement: function MDA_findElement(aRequest) {
1445     let command_id = this.command_id = this.getCommandId();
1446     if (this.context == "chrome") {
1447       let id;
1448       try {
1449         let on_success = this.sendResponse.bind(this);
1450         let on_error = this.sendError.bind(this);
1451         id = this.curBrowser.elementManager.find(
1452                               this.getCurrentWindow(),
1453                               aRequest,
1454                               on_success,
1455                               on_error,
1456                               false,
1457                               command_id);
1458       }
1459       catch (e) {
1460         this.sendError(e.message, e.code, e.stack, command_id);
1461         return;
1462       }
1463     }
1464     else {
1465       this.sendAsync("findElementContent", {value: aRequest.value,
1466                                             using: aRequest.using,
1467                                             element: aRequest.element,
1468                                             command_id: command_id});
1469     }
1470   },
1472   /**
1473    * Find elements using the indicated search strategy.
1474    *
1475    * @param object aRequest
1476    *        'using' member indicates which search method to use
1477    *        'value' member is the value the client is looking for
1478    */
1479   findElements: function MDA_findElements(aRequest) {
1480     let command_id = this.command_id = this.getCommandId();
1481     if (this.context == "chrome") {
1482       let id;
1483       try {
1484         let on_success = this.sendResponse.bind(this);
1485         let on_error = this.sendError.bind(this);
1486         id = this.curBrowser.elementManager.find(this.getCurrentWindow(),
1487                                                  aRequest,
1488                                                  on_success,
1489                                                  on_error,
1490                                                  true,
1491                                                  command_id);
1492       }
1493       catch (e) {
1494         this.sendError(e.message, e.code, e.stack, command_id);
1495         return;
1496       }
1497     }
1498     else {
1499       this.sendAsync("findElementsContent", {value: aRequest.value,
1500                                              using: aRequest.using,
1501                                              element: aRequest.element,
1502                                              command_id: command_id});
1503     }
1504   },
1506   /**
1507    * Return the active element on the page
1508    */
1509   getActiveElement: function MDA_getActiveElement(){
1510     let command_id = this.command_id = this.getCommandId();
1511     this.sendAsync("getActiveElement", {command_id: command_id});
1512   },
1514   /**
1515    * Send click event to element
1516    *
1517    * @param object aRequest
1518    *        'element' member holds the reference id to
1519    *        the element that will be clicked
1520    */
1521   clickElement: function MDA_clickElement(aRequest) {
1522     let command_id = this.command_id = this.getCommandId();
1523     if (this.context == "chrome") {
1524       try {
1525         //NOTE: click atom fails, fall back to click() action
1526         let el = this.curBrowser.elementManager.getKnownElement(
1527             aRequest.element, this.getCurrentWindow());
1528         el.click();
1529         this.sendOk(command_id);
1530       }
1531       catch (e) {
1532         this.sendError(e.message, e.code, e.stack, command_id);
1533       }
1534     }
1535     else {
1536       // We need to protect against the click causing an OOP frame to close. 
1537       // This fires the mozbrowserclose event when it closes so we need to 
1538       // listen for it and then just send an error back. The person making the
1539       // call should be aware something isnt right and handle accordingly
1540       let curWindow = this.getCurrentWindow();
1541       let self = this;
1542       this.mozBrowserClose = function() { 
1543         curWindow.removeEventListener('mozbrowserclose', self.mozBrowserClose, true);
1544         self.switchToGlobalMessageManager();
1545         self.sendError("The frame closed during the click, recovering to allow further communications", 500, null, command_id);
1546       };
1547       curWindow.addEventListener('mozbrowserclose', this.mozBrowserClose, true);
1548       this.sendAsync("clickElement", {element: aRequest.element,
1549                                       command_id: command_id});
1550     }
1551   },
1553   /**
1554    * Get a given attribute of an element
1555    *
1556    * @param object aRequest
1557    *        'element' member holds the reference id to
1558    *        the element that will be inspected
1559    *        'name' member holds the name of the attribute to retrieve
1560    */
1561   getElementAttribute: function MDA_getElementAttribute(aRequest) {
1562     let command_id = this.command_id = this.getCommandId();
1563     if (this.context == "chrome") {
1564       try {
1565         let el = this.curBrowser.elementManager.getKnownElement(
1566             aRequest.element, this.getCurrentWindow());
1567         this.sendResponse(utils.getElementAttribute(el, aRequest.name),
1568                           command_id);
1569       }
1570       catch (e) {
1571         this.sendError(e.message, e.code, e.stack, command_id);
1572       }
1573     }
1574     else {
1575       this.sendAsync("getElementAttribute", {element: aRequest.element,
1576                                              name: aRequest.name,
1577                                              command_id: command_id});
1578     }
1579   },
1581   /**
1582    * Get the text of an element, if any. Includes the text of all child elements.
1583    *
1584    * @param object aRequest
1585    *        'element' member holds the reference id to
1586    *        the element that will be inspected 
1587    */
1588   getElementText: function MDA_getElementText(aRequest) {
1589     let command_id = this.command_id = this.getCommandId();
1590     if (this.context == "chrome") {
1591       //Note: for chrome, we look at text nodes, and any node with a "label" field
1592       try {
1593         let el = this.curBrowser.elementManager.getKnownElement(
1594             aRequest.element, this.getCurrentWindow());
1595         let lines = [];
1596         this.getVisibleText(el, lines);
1597         lines = lines.join("\n");
1598         this.sendResponse(lines, command_id);
1599       }
1600       catch (e) {
1601         this.sendError(e.message, e.code, e.stack, command_id);
1602       }
1603     }
1604     else {
1605       this.sendAsync("getElementText", {element: aRequest.element,
1606                                         command_id: command_id});
1607     }
1608   },
1610   /**
1611    * Get the tag name of the element.
1612    *
1613    * @param object aRequest
1614    *        'element' member holds the reference id to
1615    *        the element that will be inspected 
1616    */
1617   getElementTagName: function MDA_getElementTagName(aRequest) {
1618     let command_id = this.command_id = this.getCommandId();
1619     if (this.context == "chrome") {
1620       try {
1621         let el = this.curBrowser.elementManager.getKnownElement(
1622             aRequest.element, this.getCurrentWindow());
1623         this.sendResponse(el.tagName.toLowerCase(), command_id);
1624       }
1625       catch (e) {
1626         this.sendError(e.message, e.code, e.stack, command_id);
1627       }
1628     }
1629     else {
1630       this.sendAsync("getElementTagName", {element: aRequest.element,
1631                                            command_id: command_id});
1632     }
1633   },
1635   /**
1636    * Check if element is displayed
1637    *
1638    * @param object aRequest
1639    *        'element' member holds the reference id to
1640    *        the element that will be checked 
1641    */
1642   isElementDisplayed: function MDA_isElementDisplayed(aRequest) {
1643     let command_id = this.command_id = this.getCommandId();
1644     if (this.context == "chrome") {
1645       try {
1646         let el = this.curBrowser.elementManager.getKnownElement(
1647             aRequest.element, this.getCurrentWindow());
1648         this.sendResponse(utils.isElementDisplayed(el), command_id);
1649       }
1650       catch (e) {
1651         this.sendError(e.message, e.code, e.stack, command_id);
1652       }
1653     }
1654     else {
1655       this.sendAsync("isElementDisplayed", {element:aRequest.element,
1656                                             command_id: command_id});
1657     }
1658   },
1660   /**
1661    * Check if element is enabled
1662    *
1663    * @param object aRequest
1664    *        'element' member holds the reference id to
1665    *        the element that will be checked
1666    */
1667   isElementEnabled: function MDA_isElementEnabled(aRequest) {
1668     let command_id = this.command_id = this.getCommandId();
1669     if (this.context == "chrome") {
1670       try {
1671         //Selenium atom doesn't quite work here
1672         let el = this.curBrowser.elementManager.getKnownElement(
1673             aRequest.element, this.getCurrentWindow());
1674         if (el.disabled != undefined) {
1675           this.sendResponse(!!!el.disabled, command_id);
1676         }
1677         else {
1678         this.sendResponse(true, command_id);
1679         }
1680       }
1681       catch (e) {
1682         this.sendError(e.message, e.code, e.stack, command_id);
1683       }
1684     }
1685     else {
1686       this.sendAsync("isElementEnabled", {element:aRequest.element,
1687                                           command_id: command_id});
1688     }
1689   },
1691   /**
1692    * Check if element is selected
1693    *
1694    * @param object aRequest
1695    *        'element' member holds the reference id to
1696    *        the element that will be checked
1697    */
1698   isElementSelected: function MDA_isElementSelected(aRequest) {
1699     let command_id = this.command_id = this.getCommandId();
1700     if (this.context == "chrome") {
1701       try {
1702         //Selenium atom doesn't quite work here
1703         let el = this.curBrowser.elementManager.getKnownElement(
1704             aRequest.element, this.getCurrentWindow());
1705         if (el.checked != undefined) {
1706           this.sendResponse(!!el.checked, command_id);
1707         }
1708         else if (el.selected != undefined) {
1709           this.sendResponse(!!el.selected, command_id);
1710         }
1711         else {
1712           this.sendResponse(true, command_id);
1713         }
1714       }
1715       catch (e) {
1716         this.sendError(e.message, e.code, e.stack, command_id);
1717       }
1718     }
1719     else {
1720       this.sendAsync("isElementSelected", {element:aRequest.element,
1721                                            command_id: command_id});
1722     }
1723   },
1725   getElementSize: function MDA_getElementSize(aRequest) {
1726     let command_id = this.command_id = this.getCommandId();
1727     if (this.context == "chrome") {
1728       try {
1729         let el = this.curBrowser.elementManager.getKnownElement(
1730             aRequest.element, this.getCurrentWindow());
1731         let clientRect = el.getBoundingClientRect();  
1732         this.sendResponse({width: clientRect.width, height: clientRect.height},
1733                           command_id);
1734       }
1735       catch (e) {
1736         this.sendError(e.message, e.code, e.stack, command_id);
1737       }
1738     }
1739     else {
1740       this.sendAsync("getElementSize", {element:aRequest.element,
1741                                         command_id: command_id});
1742     }
1743   },
1745   /**
1746    * Send key presses to element after focusing on it
1747    *
1748    * @param object aRequest
1749    *        'element' member holds the reference id to
1750    *        the element that will be checked
1751    *        'value' member holds the value to send to the element
1752    */
1753   sendKeysToElement: function MDA_sendKeysToElement(aRequest) {
1754     let command_id = this.command_id = this.getCommandId();
1755     if (this.context == "chrome") {
1756       try {
1757         let el = this.curBrowser.elementManager.getKnownElement(
1758             aRequest.element, this.getCurrentWindow());
1759         el.focus();
1760         utils.sendString(aRequest.value.join(""), utils.window);
1761         this.sendOk(command_id);
1762       }
1763       catch (e) {
1764         this.sendError(e.message, e.code, e.stack, command_id);
1765       }
1766     }
1767     else {
1768       this.sendAsync("sendKeysToElement", {element:aRequest.element,
1769                                            value: aRequest.value,
1770                                            command_id: command_id});
1771     }
1772   },
1774   /**
1775    * Sets the test name
1776    *
1777    * The test name is used in logging messages.
1778    */
1779   setTestName: function MDA_setTestName(aRequest) {
1780     this.command_id = this.getCommandId();
1781     this.logRequest("setTestName", aRequest);
1782     this.testName = aRequest.value;
1783     this.sendAsync("setTestName", {value: aRequest.value,
1784                                    command_id: this.command_id});
1785   },
1787   /**
1788    * Clear the text of an element
1789    *
1790    * @param object aRequest
1791    *        'element' member holds the reference id to
1792    *        the element that will be cleared 
1793    */
1794   clearElement: function MDA_clearElement(aRequest) {
1795     let command_id = this.command_id = this.getCommandId();
1796     if (this.context == "chrome") {
1797       //the selenium atom doesn't work here
1798       try {
1799         let el = this.curBrowser.elementManager.getKnownElement(
1800             aRequest.element, this.getCurrentWindow());
1801         if (el.nodeName == "textbox") {
1802           el.value = "";
1803         }
1804         else if (el.nodeName == "checkbox") {
1805           el.checked = false;
1806         }
1807         this.sendOk(command_id);
1808       }
1809       catch (e) {
1810         this.sendError(e.message, e.code, e.stack, command_id);
1811       }
1812     }
1813     else {
1814       this.sendAsync("clearElement", {element:aRequest.element,
1815                                       command_id: command_id});
1816     }
1817   },
1819   getElementPosition: function MDA_getElementPosition(aRequest) {
1820     this.command_id = this.getCommandId();
1821     this.sendAsync("getElementPosition", {element:aRequest.element,
1822                                           command_id: this.command_id});
1823   },
1825   /**
1826    * Add a cookie to the document.
1827    */
1828   addCookie: function MDA_addCookie(aRequest) {
1829     this.command_id = this.getCommandId();
1830     this.sendAsync("addCookie", {cookie:aRequest.cookie,
1831                                  command_id: this.command_id});
1832   },
1834   /**
1835    * Get all visible cookies for a document
1836    */
1837   getAllCookies: function MDA_getAllCookies() {
1838     this.command_id = this.getCommandId();
1839     this.sendAsync("getAllCookies", {command_id: this.command_id});
1840   },
1842   /**
1843    * Delete all cookies that are visible to a document
1844    */
1845   deleteAllCookies: function MDA_deleteAllCookies() {
1846     this.command_id = this.getCommandId();
1847     this.sendAsync("deleteAllCookies", {command_id: this.command_id});
1848   },
1850   /**
1851    * Delete a cookie by name
1852    */
1853   deleteCookie: function MDA_deleteCookie(aRequest) {
1854     this.command_id = this.getCommandId();
1855     this.sendAsync("deleteCookie", {name:aRequest.name,
1856                                     command_id: this.command_id});
1857   },
1859   /**
1860    * Closes the Browser Window.
1861    *
1862    * If it is B2G it returns straight away and does not do anything
1863    *
1864    * If is desktop it calculates how many windows are open and if there is only 
1865    * 1 then it deletes the session otherwise it closes the window
1866    */
1867   closeWindow: function MDA_closeWindow() {
1868     let command_id = this.command_id = this.getCommandId();
1869     if (appName == "B2G") {
1870       // We can't close windows so just return
1871       this.sendOk(command_id);
1872     }
1873     else {
1874       // Get the total number of windows
1875       let numOpenWindows = 0;
1876       let winEnum = this.getWinEnumerator();
1877       while (winEnum.hasMoreElements()) {
1878         numOpenWindows += 1;
1879         winEnum.getNext(); 
1880       }
1882       // if there is only 1 window left, delete the session
1883       if (numOpenWindows === 1){
1884         this.deleteSession();
1885         return;
1886       }
1888       try{
1889         this.messageManager.removeDelayedFrameScript(FRAME_SCRIPT); 
1890         this.getCurrentWindow().close();
1891         this.sendOk(command_id);
1892       }
1893       catch (e) {
1894         this.sendError("Could not close window: " + e.message, 13, e.stack,
1895                        command_id);
1896       }
1897     }
1898   }, 
1900   /**
1901    * Deletes the session.
1902    * 
1903    * If it is a desktop environment, it will close the session's tab and close all listeners
1904    *
1905    * If it is a B2G environment, it will make the main content listener sleep, and close
1906    * all other listeners. The main content listener persists after disconnect (it's the homescreen),
1907    * and can safely be reused.
1908    */
1909   deleteSession: function MDA_deleteSession() {
1910     let command_id = this.command_id = this.getCommandId();
1911     if (this.curBrowser != null) {
1912       if (appName == "B2G") {
1913         this.globalMessageManager.broadcastAsyncMessage(
1914             "Marionette:sleepSession" + this.curBrowser.mainContentId, {});
1915         this.curBrowser.knownFrames.splice(
1916             this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1);
1917       }
1918       else {
1919         //don't set this pref for B2G since the framescript can be safely reused
1920         Services.prefs.setBoolPref("marionette.contentListener", false);
1921       }
1922       this.curBrowser.closeTab();
1923       //delete session in each frame in each browser
1924       for (let win in this.browsers) {
1925         for (let i in this.browsers[win].knownFrames) {
1926           this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
1927         }
1928       }
1929       let winEnum = this.getWinEnumerator();
1930       while (winEnum.hasMoreElements()) {
1931         winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT); 
1932       }
1933     }
1934     this.sendOk(command_id);
1935     this.removeMessageManagerListeners(this.globalMessageManager);
1936     this.switchToGlobalMessageManager();
1937     // reset frame to the top-most frame
1938     this.curFrame = null;
1939     if (this.mainFrame) {
1940       this.mainFrame.focus();
1941     }
1942     this.curBrowser = null;
1943     try {
1944       this.importedScripts.remove(false);
1945     }
1946     catch (e) {
1947     }
1948   },
1950   /**
1951    * Returns the current status of the Application Cache
1952    */
1953   getAppCacheStatus: function MDA_getAppCacheStatus(aRequest) {
1954     this.command_id = this.getCommandId();
1955     this.sendAsync("getAppCacheStatus", {command_id: this.command_id});
1956   },
1958   _emu_cb_id: 0,
1959   _emu_cbs: null,
1960   runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
1961     if (callback) {
1962       if (!this._emu_cbs) {
1963         this._emu_cbs = {};
1964       }
1965       this._emu_cbs[this._emu_cb_id] = callback;
1966     }
1967     this.sendToClient({emulator_cmd: cmd, id: this._emu_cb_id}, -1);
1968     this._emu_cb_id += 1;
1969   },
1971   emulatorCmdResult: function emulatorCmdResult(message) {
1972     if (this.context != "chrome") {
1973       this.sendAsync("emulatorCmdResult", message);
1974       return;
1975     }
1977     if (!this._emu_cbs) {
1978       return;
1979     }
1981     let cb = this._emu_cbs[message.id];
1982     delete this._emu_cbs[message.id];
1983     if (!cb) {
1984       return;
1985     }
1986     try {
1987       cb(message.result);
1988     }
1989     catch(e) {
1990       this.sendError(e.message, e.code, e.stack, -1);
1991       return;
1992     }
1993   },
1994   
1995   importScript: function MDA_importScript(aRequest) {
1996     let command_id = this.command_id = this.getCommandId();
1997     if (this.context == "chrome") {
1998       let file;
1999       if (this.importedScripts.exists()) {
2000         file = FileUtils.openFileOutputStream(this.importedScripts,
2001             FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY);
2002       }
2003       else {
2004         //Note: The permission bits here don't actually get set (bug 804563)
2005         this.importedScripts.createUnique(
2006             Components.interfaces.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
2007         file = FileUtils.openFileOutputStream(this.importedScripts,
2008             FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
2009         this.importedScripts.permissions = parseInt("0666", 8); //actually set permissions
2010       }
2011       file.write(aRequest.script, aRequest.script.length);
2012       file.close();
2013       this.sendOk(command_id);
2014     }
2015     else {
2016       this.sendAsync("importScript", {script: aRequest.script,
2017                                       command_id: command_id});
2018     }
2019   },
2021   /**
2022    * Takes a screenshot of a DOM node. If there is no node given a screenshot
2023    * of the window will be taken.
2024    */
2025   screenShot: function MDA_saveScreenshot(aRequest) {
2026     this.command_id = this.getCommandId();
2027     this.sendAsync("screenShot", {element: aRequest.element,
2028                                   highlights: aRequest.highlights,
2029                                   command_id: this.command_id});
2030   },
2032   /**
2033    * Helper function to convert an outerWindowID into a UID that Marionette
2034    * tracks.
2035    */
2036   generateFrameId: function MDA_generateFrameId(id) {
2037     let uid = id + (appName == "B2G" ? "-b2g" : "");
2038     return uid;
2039   },
2041   /**
2042    * Receives all messages from content messageManager
2043    */
2044   receiveMessage: function MDA_receiveMessage(message) {
2045     // We need to just check if we need to remove the mozbrowserclose listener
2046     if (this.mozBrowserClose !== null){
2047       let curWindow = this.getCurrentWindow();
2048       curWindow.removeEventListener('mozbrowserclose', this.mozBrowserClose, true);
2049       this.mozBrowserClose = null;
2050     }
2052     switch (message.name) {
2053       case "Marionette:done":
2054         this.sendResponse(message.json.value, message.json.command_id);
2055         break;
2056       case "Marionette:ok":
2057         this.sendOk(message.json.command_id);
2058         break;
2059       case "Marionette:error":
2060         this.sendError(message.json.message, message.json.status, message.json.stacktrace, message.json.command_id);
2061         break;
2062       case "Marionette:log":
2063         //log server-side messages
2064         logger.info(message.json.message);
2065         break;
2066       case "Marionette:shareData":
2067         //log messages from tests
2068         if (message.json.log) {
2069           this.marionetteLog.addLogs(message.json.log);
2070         }
2071         if (message.json.perf) {
2072           this.marionettePerf.appendPerfData(message.json.perf);
2073         }
2074         break;
2075       case "Marionette:runEmulatorCmd":
2076         this.sendToClient(message.json, -1);
2077         break;
2078       case "Marionette:switchToFrame":
2079         // Switch to a remote frame.
2080         let thisWin = this.getCurrentWindow();
2081         let frameWindow = thisWin.QueryInterface(Ci.nsIInterfaceRequestor)
2082                                  .getInterface(Ci.nsIDOMWindowUtils)
2083                                  .getOuterWindowWithId(message.json.win);
2084         let thisFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame];
2085         let mm = thisFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
2087         // See if this frame already has our frame script loaded in it; if so,
2088         // just wake it up.
2089         for (let i = 0; i < remoteFrames.length; i++) {
2090           let frame = remoteFrames[i];
2091           if ((frame.messageManager == mm)) {
2092             this.currentRemoteFrame = frame;
2093             this.currentRemoteFrame.command_id = message.json.command_id;
2094             this.messageManager = frame.messageManager;
2095             this.addMessageManagerListeners(this.messageManager);
2096             this.messageManager.sendAsyncMessage("Marionette:restart", {});
2097             return;
2098           }
2099         }
2101         // Load the frame script in this frame, and set the frame's ChromeMessageSender
2102         // as the active message manager.
2103         this.addMessageManagerListeners(mm);
2104         mm.loadFrameScript(FRAME_SCRIPT, true);
2105         this.messageManager = mm;
2106         let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame);
2107         aFrame.messageManager = this.messageManager;
2108         aFrame.command_id = message.json.command_id;
2109         remoteFrames.push(aFrame);
2110         this.currentRemoteFrame = aFrame;
2111         break;
2112       case "Marionette:register":
2113         // This code processes the content listener's registration information
2114         // and either accepts the listener, or ignores it
2115         let nullPrevious = (this.curBrowser.curFrameId == null);
2116         let curWin = this.getCurrentWindow();
2117         let listenerWindow = curWin.QueryInterface(Ci.nsIInterfaceRequestor)
2118                                    .getInterface(Ci.nsIDOMWindowUtils)
2119                                    .getOuterWindowWithId(message.json.value);
2121         if (!listenerWindow || (listenerWindow.location.href != message.json.href) &&
2122             (this.currentRemoteFrame !== null)) {
2123           // The outerWindowID from an OOP frame will not be meaningful to
2124           // the parent process here, since each process maintains its own
2125           // independent window list.  So, it will either be null (!listenerWindow)
2126           // or it will point to some random window, which will hopefully 
2127           // cause an href mistmach.  Currently this only happens
2128           // in B2G for OOP frames registered in Marionette:switchToFrame, so
2129           // we'll acknowledge the switchToFrame message here.
2130           // XXX: Should have a better way of determining that this message
2131           // is from a remote frame.
2132           this.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value);
2133           this.sendAsync(
2134               "setState",
2135               {scriptTimeout: this.scriptTimeout,
2136                searchTimeout: this.curBrowser.elementManager.searchTimeout,
2137                command_id: this.currentRemoteFrame.command_id});
2138         }
2140         let browserType;
2141         try {
2142           browserType = message.target.getAttribute("type");
2143         } catch (ex) {
2144           // browserType remains undefined.
2145         }
2146         let reg = {};
2147         if (!browserType || browserType != "content") {
2148           reg.id = this.curBrowser.register(this.generateFrameId(message.json.value),
2149                                          message.json.href); 
2150         }
2151         this.curBrowser.elementManager.seenItems[reg.id] = listenerWindow; //add to seenItems
2152         reg.importedScripts = this.importedScripts.path;
2153         if (nullPrevious && (this.curBrowser.curFrameId != null)) {
2154           this.sendAsync("newSession", {B2G: (appName == "B2G")});
2155           if (this.curBrowser.newSession) {
2156             this.sendResponse(reg.id, this.newSessionCommandId);
2157             this.newSessionCommandId = null;
2158           }
2159         }
2160         return reg;
2161     }
2162   }
2165 MarionetteDriverActor.prototype.requestTypes = {
2166   "newSession": MarionetteDriverActor.prototype.newSession,
2167   "getSessionCapabilities": MarionetteDriverActor.prototype.getSessionCapabilities,
2168   "getStatus": MarionetteDriverActor.prototype.getStatus,
2169   "log": MarionetteDriverActor.prototype.log,
2170   "getLogs": MarionetteDriverActor.prototype.getLogs,
2171   "addPerfData": MarionetteDriverActor.prototype.addPerfData,
2172   "getPerfData": MarionetteDriverActor.prototype.getPerfData,
2173   "setContext": MarionetteDriverActor.prototype.setContext,
2174   "executeScript": MarionetteDriverActor.prototype.execute,
2175   "setScriptTimeout": MarionetteDriverActor.prototype.setScriptTimeout,
2176   "timeouts": MarionetteDriverActor.prototype.timeouts,
2177   "singleTap": MarionetteDriverActor.prototype.singleTap,
2178   "doubleTap": MarionetteDriverActor.prototype.doubleTap,
2179   "press": MarionetteDriverActor.prototype.press,
2180   "release": MarionetteDriverActor.prototype.release,
2181   "cancelTouch": MarionetteDriverActor.prototype.cancelTouch,
2182   "actionChain": MarionetteDriverActor.prototype.actionChain,
2183   "multiAction": MarionetteDriverActor.prototype.multiAction,
2184   "executeAsyncScript": MarionetteDriverActor.prototype.executeWithCallback,
2185   "executeJSScript": MarionetteDriverActor.prototype.executeJSScript,
2186   "setSearchTimeout": MarionetteDriverActor.prototype.setSearchTimeout,
2187   "findElement": MarionetteDriverActor.prototype.findElement,
2188   "findElements": MarionetteDriverActor.prototype.findElements,
2189   "clickElement": MarionetteDriverActor.prototype.clickElement,
2190   "getElementAttribute": MarionetteDriverActor.prototype.getElementAttribute,
2191   "getElementText": MarionetteDriverActor.prototype.getElementText,
2192   "getElementTagName": MarionetteDriverActor.prototype.getElementTagName,
2193   "isElementDisplayed": MarionetteDriverActor.prototype.isElementDisplayed,
2194   "getElementSize": MarionetteDriverActor.prototype.getElementSize,
2195   "isElementEnabled": MarionetteDriverActor.prototype.isElementEnabled,
2196   "isElementSelected": MarionetteDriverActor.prototype.isElementSelected,
2197   "sendKeysToElement": MarionetteDriverActor.prototype.sendKeysToElement,
2198   "getElementPosition": MarionetteDriverActor.prototype.getElementPosition,
2199   "clearElement": MarionetteDriverActor.prototype.clearElement,
2200   "getTitle": MarionetteDriverActor.prototype.getTitle,
2201   "getPageSource": MarionetteDriverActor.prototype.getPageSource,
2202   "goUrl": MarionetteDriverActor.prototype.goUrl,
2203   "getUrl": MarionetteDriverActor.prototype.getUrl,
2204   "goBack": MarionetteDriverActor.prototype.goBack,
2205   "goForward": MarionetteDriverActor.prototype.goForward,
2206   "refresh":  MarionetteDriverActor.prototype.refresh,
2207   "getWindow":  MarionetteDriverActor.prototype.getWindow,
2208   "getWindows":  MarionetteDriverActor.prototype.getWindows,
2209   "switchToFrame": MarionetteDriverActor.prototype.switchToFrame,
2210   "switchToWindow": MarionetteDriverActor.prototype.switchToWindow,
2211   "deleteSession": MarionetteDriverActor.prototype.deleteSession,
2212   "emulatorCmdResult": MarionetteDriverActor.prototype.emulatorCmdResult,
2213   "importScript": MarionetteDriverActor.prototype.importScript,
2214   "getAppCacheStatus": MarionetteDriverActor.prototype.getAppCacheStatus,
2215   "closeWindow": MarionetteDriverActor.prototype.closeWindow,
2216   "setTestName": MarionetteDriverActor.prototype.setTestName,
2217   "screenShot": MarionetteDriverActor.prototype.screenShot,
2218   "addCookie": MarionetteDriverActor.prototype.addCookie,
2219   "getAllCookies": MarionetteDriverActor.prototype.getAllCookies,
2220   "deleteAllCookies": MarionetteDriverActor.prototype.deleteAllCookies,
2221   "deleteCookie": MarionetteDriverActor.prototype.deleteCookie,
2222   "getActiveElement": MarionetteDriverActor.prototype.getActiveElement
2226  * Creates a BrowserObj. BrowserObjs handle interactions with the
2227  * browser, according to the current environment (desktop, b2g, etc.)
2229  * @param nsIDOMWindow win
2230  *        The window whose browser needs to be accessed
2231  */
2233 function BrowserObj(win) {
2234   this.DESKTOP = "desktop";
2235   this.B2G = "B2G";
2236   this.browser;
2237   this.tab = null;
2238   this.window = win;
2239   this.knownFrames = [];
2240   this.curFrameId = null;
2241   this.startPage = "about:blank";
2242   this.mainContentId = null; // used in B2G to identify the homescreen content page
2243   this.newSession = true; //used to set curFrameId upon new session
2244   this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
2245   this.setBrowser(win);
2248 BrowserObj.prototype = {
2249   /**
2250    * Set the browser if the application is not B2G
2251    *
2252    * @param nsIDOMWindow win
2253    *        current window reference
2254    */
2255   setBrowser: function BO_setBrowser(win) {
2256     switch (appName) {
2257       case "Firefox":
2258         this.browser = win.gBrowser;
2259         break;
2260       case "Fennec":
2261         this.browser = win.BrowserApp;
2262         break;
2263     }
2264   },
2265   /**
2266    * Called when we start a session with this browser.
2267    *
2268    * In a desktop environment, if newTab is true, it will start 
2269    * a new 'about:blank' tab and change focus to this tab.
2270    *
2271    * This will also set the active messagemanager for this object
2272    *
2273    * @param boolean newTab
2274    *        If true, create new tab
2275    */
2276   startSession: function BO_startSession(newTab, win, callback) {
2277     if (appName != "Firefox") {
2278       callback(win, newTab);
2279     }
2280     else if (newTab) {
2281       this.addTab(this.startPage);
2282       //if we have a new tab, make it the selected tab
2283       this.browser.selectedTab = this.tab;
2284       let newTabBrowser = this.browser.getBrowserForTab(this.tab);
2285       // wait for tab to be loaded
2286       newTabBrowser.addEventListener("load", function onLoad() {
2287         newTabBrowser.removeEventListener("load", onLoad, true);
2288         callback(win, newTab);
2289       }, true);
2290     }
2291     else {
2292       //set this.tab to the currently focused tab
2293       if (this.browser != undefined && this.browser.selectedTab != undefined) {
2294         this.tab = this.browser.selectedTab;
2295       }
2296       callback(win, newTab);
2297     }
2298   },
2300   /**
2301    * Closes current tab
2302    */
2303   closeTab: function BO_closeTab() {
2304     if (this.tab != null && (appName != "B2G")) {
2305       this.browser.removeTab(this.tab);
2306       this.tab = null;
2307     }
2308   },
2310   /**
2311    * Opens a tab with given uri
2312    *
2313    * @param string uri
2314    *      URI to open
2315    */
2316   addTab: function BO_addTab(uri) {
2317     this.tab = this.browser.addTab(uri, true);
2318   },
2320   /**
2321    * Loads content listeners if we don't already have them
2322    *
2323    * @param string script
2324    *        path of script to load
2325    * @param nsIDOMWindow frame
2326    *        frame to load the script in
2327    */
2328   loadFrameScript: function BO_loadFrameScript(script, frame) {
2329     frame.window.messageManager.loadFrameScript(script, true);
2330     Services.prefs.setBoolPref("marionette.contentListener", true);
2331   },
2333   /**
2334    * Registers a new frame, and sets its current frame id to this frame
2335    * if it is not already assigned, and if a) we already have a session 
2336    * or b) we're starting a new session and it is the right start frame.
2337    *
2338    * @param string uid
2339    *        frame uid
2340    * @param string href
2341    *        frame's href 
2342    */
2343   register: function BO_register(uid, href) {
2344     if (this.curFrameId == null) {
2345       if ((!this.newSession) || (this.newSession && 
2346           ((appName != "Firefox") || href.indexOf(this.startPage) > -1))) {
2347         this.curFrameId = uid;
2348         this.mainContentId = uid;
2349       }
2350     }
2351     this.knownFrames.push(uid); //used to delete sessions
2352     return uid;
2353   },