testing/marionette/marionette-listener.js

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

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

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
     8 let uuidGen = Cc["@mozilla.org/uuid-generator;1"]
     9                 .getService(Ci.nsIUUIDGenerator);
    11 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
    12                .getService(Ci.mozIJSSubScriptLoader);
    14 loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
    15 loader.loadSubScript("chrome://marionette/content/marionette-common.js");
    16 Cu.import("chrome://marionette/content/marionette-elements.js");
    17 Cu.import("resource://gre/modules/FileUtils.jsm");
    18 Cu.import("resource://gre/modules/NetUtil.jsm");
    19 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    20 let utils = {};
    21 utils.window = content;
    22 // Load Event/ChromeUtils for use with JS scripts:
    23 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
    24 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
    25 loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
    26 loader.loadSubScript("chrome://marionette/content/marionette-sendkeys.js", utils);
    28 loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js");
    29 loader.loadSubScript("chrome://specialpowers/content/specialpowers.js");
    31 let marionetteLogObj = new MarionetteLogObj();
    33 let isB2G = false;
    35 let marionetteTestName;
    36 let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
    37                      .getInterface(Ci.nsIDOMWindowUtils);
    38 let listenerId = null; //unique ID of this listener
    39 let curFrame = content;
    40 let previousFrame = null;
    41 let elementManager = new ElementManager([]);
    42 let importedScripts = null;
    43 let inputSource = null;
    45 // The sandbox we execute test scripts in. Gets lazily created in
    46 // createExecuteContentSandbox().
    47 let sandbox;
    49 // the unload handler
    50 let onunload;
    52 // Flag to indicate whether an async script is currently running or not.
    53 let asyncTestRunning = false;
    54 let asyncTestCommandId;
    55 let asyncTestTimeoutId;
    57 let inactivityTimeoutId = null;
    58 let heartbeatCallback = function () {}; // Called by the simpletest methods.
    60 let originalOnError;
    61 //timer for doc changes
    62 let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    63 //timer for readystate
    64 let readyStateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    65 // Send move events about this often
    66 let EVENT_INTERVAL = 30; // milliseconds
    67 // For assigning unique ids to all touches
    68 let nextTouchId = 1000;
    69 //Keep track of active Touches
    70 let touchIds = {};
    71 // last touch for each fingerId
    72 let multiLast = {};
    73 let lastCoordinates = null;
    74 let isTap = false;
    75 let scrolling = false;
    76 // whether to send mouse event
    77 let mouseEventsOnly = false;
    79 Cu.import("resource://gre/modules/Log.jsm");
    80 let logger = Log.repository.getLogger("Marionette");
    81 logger.info("loaded marionette-listener.js");
    82 let modalHandler = function() {
    83   // This gets called on the system app only since it receives the mozbrowserprompt event
    84   sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
    85   let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
    86   if (isLocal) {
    87     previousFrame = curFrame;
    88   }
    89   curFrame = content;
    90   sandbox = null;
    91 };
    93 /**
    94  * Called when listener is first started up.
    95  * The listener sends its unique window ID and its current URI to the actor.
    96  * If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
    97  */
    98 function registerSelf() {
    99   let msg = {value: winUtil.outerWindowID, href: content.location.href};
   100   // register will have the ID and a boolean describing if this is the main process or not
   101   let register = sendSyncMessage("Marionette:register", msg);
   103   if (register[0]) {
   104     listenerId = register[0][0].id;
   105     // check if we're the main process
   106     if (register[0][1] == true) {
   107       addMessageListener("MarionetteMainListener:emitTouchEvent", emitTouchEventForIFrame);
   108     }
   109     importedScripts = FileUtils.getDir('TmpD', [], false);
   110     importedScripts.append('marionetteContentScripts');
   111     startListeners();
   112   }
   113 }
   115 function emitTouchEventForIFrame(message) {
   116   let message = message.json;
   117   let frames = curFrame.document.getElementsByTagName("iframe");
   118   let iframe = frames[message.index];
   119   let identifier = touchId = nextTouchId++;
   120   let tabParent = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.tabParent;
   121   tabParent.injectTouchEvent(message.type, [identifier],
   122                              [message.clientX], [message.clientY],
   123                              [message.radiusX], [message.radiusY],
   124                              [message.rotationAngle], [message.force],
   125                              1, 0);
   126 }
   128 /**
   129  * Add a message listener that's tied to our listenerId.
   130  */
   131 function addMessageListenerId(messageName, handler) {
   132   addMessageListener(messageName + listenerId, handler);
   133 }
   135 /**
   136  * Remove a message listener that's tied to our listenerId.
   137  */
   138 function removeMessageListenerId(messageName, handler) {
   139   removeMessageListener(messageName + listenerId, handler);
   140 }
   142 /**
   143  * Start all message listeners
   144  */
   145 function startListeners() {
   146   addMessageListenerId("Marionette:newSession", newSession);
   147   addMessageListenerId("Marionette:executeScript", executeScript);
   148   addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
   149   addMessageListenerId("Marionette:executeJSScript", executeJSScript);
   150   addMessageListenerId("Marionette:singleTap", singleTap);
   151   addMessageListenerId("Marionette:actionChain", actionChain);
   152   addMessageListenerId("Marionette:multiAction", multiAction);
   153   addMessageListenerId("Marionette:get", get);
   154   addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
   155   addMessageListenerId("Marionette:getTitle", getTitle);
   156   addMessageListenerId("Marionette:getPageSource", getPageSource);
   157   addMessageListenerId("Marionette:goBack", goBack);
   158   addMessageListenerId("Marionette:goForward", goForward);
   159   addMessageListenerId("Marionette:refresh", refresh);
   160   addMessageListenerId("Marionette:findElementContent", findElementContent);
   161   addMessageListenerId("Marionette:findElementsContent", findElementsContent);
   162   addMessageListenerId("Marionette:getActiveElement", getActiveElement);
   163   addMessageListenerId("Marionette:clickElement", clickElement);
   164   addMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
   165   addMessageListenerId("Marionette:getElementText", getElementText);
   166   addMessageListenerId("Marionette:getElementTagName", getElementTagName);
   167   addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   168   addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
   169   addMessageListenerId("Marionette:submitElement", submitElement);
   170   addMessageListenerId("Marionette:getElementSize", getElementSize);
   171   addMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   172   addMessageListenerId("Marionette:isElementSelected", isElementSelected);
   173   addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   174   addMessageListenerId("Marionette:getElementLocation", getElementLocation);
   175   addMessageListenerId("Marionette:clearElement", clearElement);
   176   addMessageListenerId("Marionette:switchToFrame", switchToFrame);
   177   addMessageListenerId("Marionette:deleteSession", deleteSession);
   178   addMessageListenerId("Marionette:sleepSession", sleepSession);
   179   addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   180   addMessageListenerId("Marionette:importScript", importScript);
   181   addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   182   addMessageListenerId("Marionette:setTestName", setTestName);
   183   addMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
   184   addMessageListenerId("Marionette:addCookie", addCookie);
   185   addMessageListenerId("Marionette:getCookies", getCookies);
   186   addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
   187   addMessageListenerId("Marionette:deleteCookie", deleteCookie);
   188 }
   190 /**
   191  * Used during newSession and restart, called to set up the modal dialog listener in b2g
   192  */
   193 function waitForReady() {
   194   if (content.document.readyState == 'complete') {
   195     readyStateTimer.cancel();
   196     content.addEventListener("mozbrowsershowmodalprompt", modalHandler, false);
   197     content.addEventListener("unload", waitForReady, false);
   198   }
   199   else {
   200     readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   201   }
   202 }
   204 /**
   205  * Called when we start a new session. It registers the
   206  * current environment, and resets all values
   207  */
   208 function newSession(msg) {
   209   isB2G = msg.json.B2G;
   210   resetValues();
   211   if (isB2G) {
   212     readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   213     // We have to set correct mouse event source to MOZ_SOURCE_TOUCH
   214     // to offer a way for event listeners to differentiate
   215     // events being the result of a physical mouse action.
   216     // This is especially important for the touch event shim,
   217     // in order to prevent creating touch event for these fake mouse events.
   218     inputSource = Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH;
   219   }
   220 }
   222 /**
   223  * Puts the current session to sleep, so all listeners are removed except
   224  * for the 'restart' listener. This is used to keep the content listener
   225  * alive for reuse in B2G instead of reloading it each time.
   226  */
   227 function sleepSession(msg) {
   228   deleteSession();
   229   addMessageListener("Marionette:restart", restart);
   230 }
   232 /**
   233  * Restarts all our listeners after this listener was put to sleep
   234  */
   235 function restart(msg) {
   236   removeMessageListener("Marionette:restart", restart);
   237   if (isB2G) {
   238     readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   239   }
   240   registerSelf();
   241 }
   243 /**
   244  * Removes all listeners
   245  */
   246 function deleteSession(msg) {
   247   removeMessageListenerId("Marionette:newSession", newSession);
   248   removeMessageListenerId("Marionette:executeScript", executeScript);
   249   removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
   250   removeMessageListenerId("Marionette:executeJSScript", executeJSScript);
   251   removeMessageListenerId("Marionette:singleTap", singleTap);
   252   removeMessageListenerId("Marionette:actionChain", actionChain);
   253   removeMessageListenerId("Marionette:multiAction", multiAction);
   254   removeMessageListenerId("Marionette:get", get);
   255   removeMessageListenerId("Marionette:getTitle", getTitle);
   256   removeMessageListenerId("Marionette:getPageSource", getPageSource);
   257   removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
   258   removeMessageListenerId("Marionette:goBack", goBack);
   259   removeMessageListenerId("Marionette:goForward", goForward);
   260   removeMessageListenerId("Marionette:refresh", refresh);
   261   removeMessageListenerId("Marionette:findElementContent", findElementContent);
   262   removeMessageListenerId("Marionette:findElementsContent", findElementsContent);
   263   removeMessageListenerId("Marionette:getActiveElement", getActiveElement);
   264   removeMessageListenerId("Marionette:clickElement", clickElement);
   265   removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
   266   removeMessageListenerId("Marionette:getElementTagName", getElementTagName);
   267   removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   268   removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
   269   removeMessageListenerId("Marionette:submitElement", submitElement);
   270   removeMessageListenerId("Marionette:getElementSize", getElementSize);
   271   removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   272   removeMessageListenerId("Marionette:isElementSelected", isElementSelected);
   273   removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   274   removeMessageListenerId("Marionette:getElementLocation", getElementLocation);
   275   removeMessageListenerId("Marionette:clearElement", clearElement);
   276   removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
   277   removeMessageListenerId("Marionette:deleteSession", deleteSession);
   278   removeMessageListenerId("Marionette:sleepSession", sleepSession);
   279   removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   280   removeMessageListenerId("Marionette:importScript", importScript);
   281   removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   282   removeMessageListenerId("Marionette:setTestName", setTestName);
   283   removeMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
   284   removeMessageListenerId("Marionette:addCookie", addCookie);
   285   removeMessageListenerId("Marionette:getCookies", getCookies);
   286   removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
   287   removeMessageListenerId("Marionette:deleteCookie", deleteCookie);
   288   if (isB2G) {
   289     content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
   290   }
   291   elementManager.reset();
   292   // reset frame to the top-most frame
   293   curFrame = content;
   294   curFrame.focus();
   295   touchIds = {};
   296 }
   298 /*
   299  * Helper methods
   300  */
   302 /**
   303  * Generic method to send a message to the server
   304  */
   305 function sendToServer(msg, value, command_id) {
   306   if (command_id) {
   307     value.command_id = command_id;
   308   }
   309   sendAsyncMessage(msg, value);
   310 }
   312 /**
   313  * Send response back to server
   314  */
   315 function sendResponse(value, command_id) {
   316   sendToServer("Marionette:done", value, command_id);
   317 }
   319 /**
   320  * Send ack back to server
   321  */
   322 function sendOk(command_id) {
   323   sendToServer("Marionette:ok", {}, command_id);
   324 }
   326 /**
   327  * Send log message to server
   328  */
   329 function sendLog(msg) {
   330   sendToServer("Marionette:log", { message: msg });
   331 }
   333 /**
   334  * Send error message to server
   335  */
   336 function sendError(message, status, trace, command_id) {
   337   let error_msg = { message: message, status: status, stacktrace: trace };
   338   sendToServer("Marionette:error", error_msg, command_id);
   339 }
   341 /**
   342  * Clear test values after completion of test
   343  */
   344 function resetValues() {
   345   sandbox = null;
   346   curFrame = content;
   347   mouseEventsOnly = false;
   348 }
   350 /**
   351  * Dump a logline to stdout. Prepends logline with a timestamp.
   352  */
   353 function dumpLog(logline) {
   354   dump(Date.now() + " Marionette: " + logline);
   355 }
   357 /**
   358  * Check if our context was interrupted
   359  */
   360 function wasInterrupted() {
   361   if (previousFrame) {
   362     let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2));
   363     if (element.id.indexOf("modal-dialog") == -1) {
   364       return true;
   365     }
   366     else {
   367       return false;
   368     }
   369   }
   370   return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
   371 }
   373 /*
   374  * Marionette Methods
   375  */
   377 /**
   378  * Returns a content sandbox that can be used by the execute_foo functions.
   379  */
   380 function createExecuteContentSandbox(aWindow, timeout) {
   381   let sandbox = new Cu.Sandbox(aWindow, {sandboxPrototype: aWindow});
   382   sandbox.global = sandbox;
   383   sandbox.window = aWindow;
   384   sandbox.document = sandbox.window.document;
   385   sandbox.navigator = sandbox.window.navigator;
   386   sandbox.testUtils = utils;
   387   sandbox.asyncTestCommandId = asyncTestCommandId;
   389   let marionette = new Marionette(this, aWindow, "content",
   390                                   marionetteLogObj, timeout,
   391                                   heartbeatCallback,
   392                                   marionetteTestName);
   393   sandbox.marionette = marionette;
   394   marionette.exports.forEach(function(fn) {
   395     try {
   396       sandbox[fn] = marionette[fn].bind(marionette);
   397     }
   398     catch(e) {
   399       sandbox[fn] = marionette[fn];
   400     }
   401   });
   403   XPCOMUtils.defineLazyGetter(sandbox, 'SpecialPowers', function() {
   404     return new SpecialPowers(aWindow);
   405   });
   407   sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) {
   408     if (commandId == asyncTestCommandId) {
   409       curFrame.removeEventListener("unload", onunload, false);
   410       curFrame.clearTimeout(asyncTestTimeoutId);
   412       if (inactivityTimeoutId != null) {
   413         curFrame.clearTimeout(inactivityTimeoutId);
   414       }
   417       sendSyncMessage("Marionette:shareData",
   418                       {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   419       marionetteLogObj.clearLogs();
   421       if (status == 0){
   422         if (Object.keys(_emu_cbs).length) {
   423           _emu_cbs = {};
   424           sendError("Emulator callback still pending when finish() called",
   425                     500, null, commandId);
   426         }
   427         else {
   428           sendResponse({value: elementManager.wrapValue(value), status: status},
   429                        commandId);
   430         }
   431       }
   432       else {
   433         sendError(value, status, stack, commandId);
   434       }
   436       asyncTestRunning = false;
   437       asyncTestTimeoutId = undefined;
   438       asyncTestCommandId = undefined;
   439       inactivityTimeoutId = null;
   440     }
   441   };
   442   sandbox.finish = function sandbox_finish() {
   443     if (asyncTestRunning) {
   444       sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId);
   445     } else {
   446       return marionette.generate_results();
   447     }
   448   };
   449   sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) {
   450     return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId);
   451   };
   453   return sandbox;
   454 }
   456 /**
   457  * Execute the given script either as a function body (executeScript)
   458  * or directly (for 'mochitest' like JS Marionette tests)
   459  */
   460 function executeScript(msg, directInject) {
   461   // Set up inactivity timeout.
   462   if (msg.json.inactivityTimeout) {
   463     let setTimer = function() {
   464         inactivityTimeoutId = curFrame.setTimeout(function() {
   465         sendError('timed out due to inactivity', 28, null, asyncTestCommandId);
   466       }, msg.json.inactivityTimeout);
   467    };
   469     setTimer();
   470     heartbeatCallback = function resetInactivityTimeout() {
   471       curFrame.clearTimeout(inactivityTimeoutId);
   472       setTimer();
   473     };
   474   }
   476   asyncTestCommandId = msg.json.command_id;
   477   let script = msg.json.script;
   479   if (msg.json.newSandbox || !sandbox) {
   480     sandbox = createExecuteContentSandbox(curFrame,
   481                                           msg.json.timeout);
   482     if (!sandbox) {
   483       sendError("Could not create sandbox!", 500, null, asyncTestCommandId);
   484       return;
   485     }
   486   }
   487   else {
   488     sandbox.asyncTestCommandId = asyncTestCommandId;
   489   }
   491   try {
   492     if (directInject) {
   493       if (importedScripts.exists()) {
   494         let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
   495                       createInstance(Components.interfaces.nsIFileInputStream);
   496         stream.init(importedScripts, -1, 0, 0);
   497         let data = NetUtil.readInputStreamToString(stream, stream.available());
   498         script = data + script;
   499       }
   500       let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file" ,0);
   501       sendSyncMessage("Marionette:shareData",
   502                       {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   503       marionetteLogObj.clearLogs();
   505       if (res == undefined || res.passed == undefined) {
   506         sendError("Marionette.finish() not called", 17, null, asyncTestCommandId);
   507       }
   508       else {
   509         sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
   510       }
   511     }
   512     else {
   513       try {
   514         sandbox.__marionetteParams = elementManager.convertWrappedArguments(
   515           msg.json.args, curFrame);
   516       }
   517       catch(e) {
   518         sendError(e.message, e.code, e.stack, asyncTestCommandId);
   519         return;
   520       }
   522       script = "let __marionetteFunc = function(){" + script + "};" +
   523                    "__marionetteFunc.apply(null, __marionetteParams);";
   524       if (importedScripts.exists()) {
   525         let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
   526                       createInstance(Components.interfaces.nsIFileInputStream);
   527         stream.init(importedScripts, -1, 0, 0);
   528         let data = NetUtil.readInputStreamToString(stream, stream.available());
   529         script = data + script;
   530       }
   531       let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
   532       sendSyncMessage("Marionette:shareData",
   533                       {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   534       marionetteLogObj.clearLogs();
   535       sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
   536     }
   537   }
   538   catch (e) {
   539     // 17 = JavascriptException
   540     let error = createStackMessage(e,
   541                                    "execute_script",
   542                                    msg.json.filename,
   543                                    msg.json.line,
   544                                    script);
   545     sendError(error[0], 17, error[1], asyncTestCommandId);
   546   }
   547 }
   549 /**
   550  * Sets the test name, used in logging messages.
   551  */
   552 function setTestName(msg) {
   553   marionetteTestName = msg.json.value;
   554   sendOk(msg.json.command_id);
   555 }
   557 /**
   558  * Execute async script
   559  */
   560 function executeAsyncScript(msg) {
   561   executeWithCallback(msg);
   562 }
   564 /**
   565  * Execute pure JS test. Handles both async and sync cases.
   566  */
   567 function executeJSScript(msg) {
   568   if (msg.json.async) {
   569     executeWithCallback(msg, msg.json.async);
   570   }
   571   else {
   572     executeScript(msg, true);
   573   }
   574 }
   576 /**
   577  * This function is used by executeAsync and executeJSScript to execute a script
   578  * in a sandbox.
   579  *
   580  * For executeJSScript, it will return a message only when the finish() method is called.
   581  * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
   582  * method is called, or if it times out.
   583  */
   584 function executeWithCallback(msg, useFinish) {
   585   // Set up inactivity timeout.
   586   if (msg.json.inactivityTimeout) {
   587     let setTimer = function() {
   588       inactivityTimeoutId = curFrame.setTimeout(function() {
   589         sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId);
   590       }, msg.json.inactivityTimeout);
   591     };
   593     setTimer();
   594     heartbeatCallback = function resetInactivityTimeout() {
   595       curFrame.clearTimeout(inactivityTimeoutId);
   596       setTimer();
   597     };
   598   }
   600   let script = msg.json.script;
   601   asyncTestCommandId = msg.json.command_id;
   603   onunload = function() {
   604     sendError("unload was called", 17, null, asyncTestCommandId);
   605   };
   606   curFrame.addEventListener("unload", onunload, false);
   608   if (msg.json.newSandbox || !sandbox) {
   609     sandbox = createExecuteContentSandbox(curFrame,
   610                                           msg.json.timeout);
   611     if (!sandbox) {
   612       sendError("Could not create sandbox!", 17, null, asyncTestCommandId);
   613       return;
   614     }
   615   }
   616   else {
   617     sandbox.asyncTestCommandId = asyncTestCommandId;
   618   }
   619   sandbox.tag = script;
   621   // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
   622   // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
   623   // However Selenium code returns 28, see
   624   // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
   625   // We'll stay compatible with the Selenium code.
   626   asyncTestTimeoutId = curFrame.setTimeout(function() {
   627     sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId);
   628   }, msg.json.timeout);
   630   originalOnError = curFrame.onerror;
   631   curFrame.onerror = function errHandler(errMsg, url, line) {
   632     sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId);
   633     curFrame.onerror = originalOnError;
   634   };
   636   let scriptSrc;
   637   if (useFinish) {
   638     if (msg.json.timeout == null || msg.json.timeout == 0) {
   639       sendError("Please set a timeout", 21, null, asyncTestCommandId);
   640     }
   641     scriptSrc = script;
   642   }
   643   else {
   644     try {
   645       sandbox.__marionetteParams = elementManager.convertWrappedArguments(
   646         msg.json.args, curFrame);
   647     }
   648     catch(e) {
   649       sendError(e.message, e.code, e.stack, asyncTestCommandId);
   650       return;
   651     }
   653     scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
   654                 "let __marionetteFunc = function() { " + script + "};" +
   655                 "__marionetteFunc.apply(null, __marionetteParams); ";
   656   }
   658   try {
   659     asyncTestRunning = true;
   660     if (importedScripts.exists()) {
   661       let stream = Cc["@mozilla.org/network/file-input-stream;1"].
   662                       createInstance(Ci.nsIFileInputStream);
   663       stream.init(importedScripts, -1, 0, 0);
   664       let data = NetUtil.readInputStreamToString(stream, stream.available());
   665       scriptSrc = data + scriptSrc;
   666     }
   667     Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0);
   668   } catch (e) {
   669     // 17 = JavascriptException
   670     let error = createStackMessage(e,
   671                                    "execute_async_script",
   672                                    msg.json.filename,
   673                                    msg.json.line,
   674                                    scriptSrc);
   675     sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId);
   676   }
   677 }
   679 /**
   680  * This function creates a touch event given a touch type and a touch
   681  */
   682 function emitTouchEvent(type, touch) {
   683   if (!wasInterrupted()) {
   684     let loggingInfo = "emitting Touch event of type " + type + " to element with id: " + touch.target.id + " and tag name: " + touch.target.tagName + " at coordinates (" + touch.clientX + ", " + touch.clientY + ") relative to the viewport";
   685     dumpLog(loggingInfo);
   686     var docShell = curFrame.document.defaultView.
   687                    QueryInterface(Components.interfaces.nsIInterfaceRequestor).
   688                    getInterface(Components.interfaces.nsIWebNavigation).
   689                    QueryInterface(Components.interfaces.nsIDocShell);
   690     if (docShell.asyncPanZoomEnabled && scrolling) {
   691       // if we're in APZ and we're scrolling, we must use injectTouchEvent to dispatch our touchmove events
   692       let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId");
   693       // only call emitTouchEventForIFrame if we're inside an iframe.
   694       if (index != null) {
   695         sendSyncMessage("Marionette:emitTouchEvent", {index: index, type: type, id: touch.identifier,
   696                                                       clientX: touch.clientX, clientY: touch.clientY,
   697                                                       radiusX: touch.radiusX, radiusY: touch.radiusY,
   698                                                       rotation: touch.rotationAngle, force: touch.force});
   699         return;
   700       }
   701     }
   702     // we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
   703     /*
   704     Disabled per bug 888303
   705     marionetteLogObj.log(loggingInfo, "TRACE");
   706     sendSyncMessage("Marionette:shareData",
   707                     {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   708     marionetteLogObj.clearLogs();
   709     */
   710     let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
   711     domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
   712   }
   713 }
   715 /**
   716  * This function emit mouse event
   717  *   @param: doc is the current document
   718  *           type is the type of event to dispatch
   719  *           clickCount is the number of clicks, button notes the mouse button
   720  *           elClientX and elClientY are the coordinates of the mouse relative to the viewport
   721  */
   722 function emitMouseEvent(doc, type, elClientX, elClientY, clickCount, button) {
   723   if (!wasInterrupted()) {
   724     let loggingInfo = "emitting Mouse event of type " + type + " at coordinates (" + elClientX + ", " + elClientY + ") relative to the viewport";
   725     dumpLog(loggingInfo);
   726     /*
   727     Disabled per bug 888303
   728     marionetteLogObj.log(loggingInfo, "TRACE");
   729     sendSyncMessage("Marionette:shareData",
   730                     {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   731     marionetteLogObj.clearLogs();
   732     */
   733     let win = doc.defaultView;
   734     let domUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
   735     domUtils.sendMouseEvent(type, elClientX, elClientY, button || 0, clickCount || 1, 0, false, 0, inputSource);
   736   }
   737 }
   739 /**
   740  * Helper function that perform a mouse tap
   741  */
   742 function mousetap(doc, x, y) {
   743   emitMouseEvent(doc, 'mousemove', x, y);
   744   emitMouseEvent(doc, 'mousedown', x, y);
   745   emitMouseEvent(doc, 'mouseup', x, y);
   746 }
   749 /**
   750  * This function generates a pair of coordinates relative to the viewport given a
   751  * target element and coordinates relative to that element's top-left corner.
   752  * @param 'x', and 'y' are the relative to the target.
   753  *        If they are not specified, then the center of the target is used.
   754  */
   755 function coordinates(target, x, y) {
   756   let box = target.getBoundingClientRect();
   757   if (x == null) {
   758     x = box.width / 2;
   759   }
   760   if (y == null) {
   761     y = box.height / 2;
   762   }
   763   let coords = {};
   764   coords.x = box.left + x;
   765   coords.y = box.top + y;
   766   return coords;
   767 }
   769 /**
   770  * This function returns if the element is in viewport
   771  */
   772 function elementInViewport(el) {
   773   let rect = el.getBoundingClientRect();
   774   let viewPort = {top: curFrame.pageYOffset,
   775                   left: curFrame.pageXOffset,
   776                   bottom: (curFrame.pageYOffset + curFrame.innerHeight),
   777                   right:(curFrame.pageXOffset + curFrame.innerWidth)};
   778   return (viewPort.left <= rect.right + curFrame.pageXOffset &&
   779           rect.left + curFrame.pageXOffset <= viewPort.right &&
   780           viewPort.top <= rect.bottom + curFrame.pageYOffset &&
   781           rect.top + curFrame.pageYOffset <= viewPort.bottom);
   782 }
   784 /**
   785  * This function throws the visibility of the element error
   786  */
   787 function checkVisible(el) {
   788   //check if the element is visible
   789   let visible = utils.isElementDisplayed(el);
   790   if (!visible) {
   791     return false;
   792   }
   793   if (el.tagName.toLowerCase() === 'body') {
   794     return true;
   795   }
   796   if (!elementInViewport(el)) {
   797     //check if scroll function exist. If so, call it.
   798     if (el.scrollIntoView) {
   799       el.scrollIntoView(false);
   800       if (!elementInViewport(el)) {
   801         return false;
   802       }
   803     }
   804     else {
   805       return false;
   806     }
   807   }
   808   return true;
   809 }
   811 //x and y are coordinates relative to the viewport
   812 function generateEvents(type, x, y, touchId, target) {
   813   lastCoordinates = [x, y];
   814   let doc = curFrame.document;
   815   switch (type) {
   816     case 'tap':
   817       if (mouseEventsOnly) {
   818         mousetap(target.ownerDocument, x, y);
   819       }
   820       else {
   821         let touchId = nextTouchId++;
   822         let touch = createATouch(target, x, y, touchId);
   823         emitTouchEvent('touchstart', touch);
   824         emitTouchEvent('touchend', touch);
   825         mousetap(target.ownerDocument, x, y);
   826       }
   827       lastCoordinates = null;
   828       break;
   829     case 'press':
   830       isTap = true;
   831       if (mouseEventsOnly) {
   832         emitMouseEvent(doc, 'mousemove', x, y);
   833         emitMouseEvent(doc, 'mousedown', x, y);
   834       }
   835       else {
   836         let touchId = nextTouchId++;
   837         let touch = createATouch(target, x, y, touchId);
   838         emitTouchEvent('touchstart', touch);
   839         touchIds[touchId] = touch;
   840         return touchId;
   841       }
   842       break;
   843     case 'release':
   844       if (mouseEventsOnly) {
   845         emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]);
   846       }
   847       else {
   848         let touch = touchIds[touchId];
   849         touch = createATouch(touch.target, lastCoordinates[0], lastCoordinates[1], touchId);
   850         emitTouchEvent('touchend', touch);
   851         if (isTap) {
   852           mousetap(touch.target.ownerDocument, touch.clientX, touch.clientY);
   853         }
   854         delete touchIds[touchId];
   855       }
   856       isTap = false;
   857       lastCoordinates = null;
   858       break;
   859     case 'cancel':
   860       isTap = false;
   861       if (mouseEventsOnly) {
   862         emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]);
   863       }
   864       else {
   865         emitTouchEvent('touchcancel', touchIds[touchId]);
   866         delete touchIds[touchId];
   867       }
   868       lastCoordinates = null;
   869       break;
   870     case 'move':
   871       isTap = false;
   872       if (mouseEventsOnly) {
   873         emitMouseEvent(doc, 'mousemove', x, y);
   874       }
   875       else {
   876         touch = createATouch(touchIds[touchId].target, x, y, touchId);
   877         touchIds[touchId] = touch;
   878         emitTouchEvent('touchmove', touch);
   879       }
   880       break;
   881     case 'contextmenu':
   882       isTap = false;
   883       let event = curFrame.document.createEvent('HTMLEvents');
   884       event.initEvent('contextmenu', true, true);
   885       if (mouseEventsOnly) {
   886         target = doc.elementFromPoint(lastCoordinates[0], lastCoordinates[1]);
   887       }
   888       else {
   889         target = touchIds[touchId].target;
   890       }
   891       target.dispatchEvent(event);
   892       break;
   893     default:
   894       throw {message:"Unknown event type: " + type, code: 500, stack:null};
   895   }
   896   if (wasInterrupted()) {
   897     if (previousFrame) {
   898       //if previousFrame is set, then we're in a single process environment
   899       curFrame = previousFrame;
   900       previousFrame = null;
   901       sandbox = null;
   902     }
   903     else {
   904       //else we're in OOP environment, so we'll switch to the original OOP frame
   905       sendSyncMessage("Marionette:switchToModalOrigin");
   906     }
   907     sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
   908   }
   909 }
   911 /**
   912  * Function that perform a single tap
   913  */
   914 function singleTap(msg) {
   915   let command_id = msg.json.command_id;
   916   try {
   917     let el = elementManager.getKnownElement(msg.json.id, curFrame);
   918     // after this block, the element will be scrolled into view
   919     if (!checkVisible(el)) {
   920        sendError("Element is not currently visible and may not be manipulated", 11, null, command_id);
   921        return;
   922     }
   923     if (!curFrame.document.createTouch) {
   924       mouseEventsOnly = true;
   925     }
   926     let c = coordinates(el, msg.json.corx, msg.json.cory);
   927     generateEvents('tap', c.x, c.y, null, el);
   928     sendOk(msg.json.command_id);
   929   }
   930   catch (e) {
   931     sendError(e.message, e.code, e.stack, msg.json.command_id);
   932   }
   933 }
   935 /**
   936  * Function to create a touch based on the element
   937  * corx and cory are relative to the viewport, id is the touchId
   938  */
   939 function createATouch(el, corx, cory, touchId) {
   940   let doc = el.ownerDocument;
   941   let win = doc.defaultView;
   942   let clientX = corx;
   943   let clientY = cory;
   944   let pageX = clientX + win.pageXOffset,
   945       pageY = clientY + win.pageYOffset;
   946   let screenX = clientX + win.mozInnerScreenX,
   947       screenY = clientY + win.mozInnerScreenY;
   948   let atouch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
   949   return atouch;
   950 }
   952 /**
   953  * Function to emit touch events for each finger. e.g. finger=[['press', id], ['wait', 5], ['release']]
   954  * touchId represents the finger id, i keeps track of the current action of the chain
   955  */
   956 function actions(chain, touchId, command_id, i) {
   957   if (typeof i === "undefined") {
   958     i = 0;
   959   }
   960   if (i == chain.length) {
   961     sendResponse({value: touchId}, command_id);
   962     return;
   963   }
   964   let pack = chain[i];
   965   let command = pack[0];
   966   let el;
   967   let c;
   968   i++;
   969   if (command != 'press') {
   970     //if mouseEventsOnly, then touchIds isn't used
   971     if (!(touchId in touchIds) && !mouseEventsOnly) {
   972       sendError("Element has not been pressed", 500, null, command_id);
   973       return;
   974     }
   975   }
   976   switch(command) {
   977     case 'press':
   978       if (lastCoordinates) {
   979         generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId);
   980         sendError("Invalid Command: press cannot follow an active touch event", 500, null, command_id);
   981         return;
   982       }
   983       // look ahead to check if we're scrolling. Needed for APZ touch dispatching.
   984       if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
   985         scrolling = true;
   986       }
   987       el = elementManager.getKnownElement(pack[1], curFrame);
   988       c = coordinates(el, pack[2], pack[3]);
   989       touchId = generateEvents('press', c.x, c.y, null, el);
   990       actions(chain, touchId, command_id, i);
   991       break;
   992     case 'release':
   993       generateEvents('release', lastCoordinates[0], lastCoordinates[1], touchId);
   994       actions(chain, null, command_id, i);
   995       scrolling =  false;
   996       break;
   997     case 'move':
   998       el = elementManager.getKnownElement(pack[1], curFrame);
   999       c = coordinates(el);
  1000       generateEvents('move', c.x, c.y, touchId);
  1001       actions(chain, touchId, command_id, i);
  1002       break;
  1003     case 'moveByOffset':
  1004       generateEvents('move', lastCoordinates[0] + pack[1], lastCoordinates[1] + pack[2], touchId);
  1005       actions(chain, touchId, command_id, i);
  1006       break;
  1007     case 'wait':
  1008       if (pack[1] != null ) {
  1009         let time = pack[1]*1000;
  1010         // standard waiting time to fire contextmenu
  1011         let standard = 750;
  1012         try {
  1013           standard = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
  1015         catch (e){}
  1016         if (time >= standard && isTap) {
  1017             chain.splice(i, 0, ['longPress'], ['wait', (time-standard)/1000]);
  1018             time = standard;
  1020         checkTimer.initWithCallback(function(){actions(chain, touchId, command_id, i);}, time, Ci.nsITimer.TYPE_ONE_SHOT);
  1022       else {
  1023         actions(chain, touchId, command_id, i);
  1025       break;
  1026     case 'cancel':
  1027       generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId);
  1028       actions(chain, touchId, command_id, i);
  1029       scrolling = false;
  1030       break;
  1031     case 'longPress':
  1032       generateEvents('contextmenu', lastCoordinates[0], lastCoordinates[1], touchId);
  1033       actions(chain, touchId, command_id, i);
  1034       break;
  1038 /**
  1039  * Function to start action chain on one finger
  1040  */
  1041 function actionChain(msg) {
  1042   let command_id = msg.json.command_id;
  1043   let args = msg.json.chain;
  1044   let touchId = msg.json.nextId;
  1045   try {
  1046     let commandArray = elementManager.convertWrappedArguments(args, curFrame);
  1047     // loop the action array [ ['press', id], ['move', id], ['release', id] ]
  1048     if (touchId == null) {
  1049       touchId = nextTouchId++;
  1051     if (!curFrame.document.createTouch) {
  1052       mouseEventsOnly = true;
  1054     actions(commandArray, touchId, command_id);
  1056   catch (e) {
  1057     sendError(e.message, e.code, e.stack, msg.json.command_id);
  1061 /**
  1062  * Function to emit touch events which allow multi touch on the screen
  1063  * @param type represents the type of event, touch represents the current touch,touches are all pending touches
  1064  */
  1065 function emitMultiEvents(type, touch, touches) {
  1066   let target = touch.target;
  1067   let doc = target.ownerDocument;
  1068   let win = doc.defaultView;
  1069   // touches that are in the same document
  1070   let documentTouches = doc.createTouchList(touches.filter(function(t) {
  1071     return ((t.target.ownerDocument === doc) && (type != 'touchcancel'));
  1072   }));
  1073   // touches on the same target
  1074   let targetTouches = doc.createTouchList(touches.filter(function(t) {
  1075     return ((t.target === target) && ((type != 'touchcancel') || (type != 'touchend')));
  1076   }));
  1077   // Create changed touches
  1078   let changedTouches = doc.createTouchList(touch);
  1079   // Create the event object
  1080   let event = doc.createEvent('TouchEvent');
  1081   event.initTouchEvent(type,
  1082                        true,
  1083                        true,
  1084                        win,
  1085                        0,
  1086                        false, false, false, false,
  1087                        documentTouches,
  1088                        targetTouches,
  1089                        changedTouches);
  1090   target.dispatchEvent(event);
  1093 /**
  1094  * Function to dispatch one set of actions
  1095  * @param touches represents all pending touches, batchIndex represents the batch we are dispatching right now
  1096  */
  1097 function setDispatch(batches, touches, command_id, batchIndex) {
  1098   if (typeof batchIndex === "undefined") {
  1099     batchIndex = 0;
  1101   // check if all the sets have been fired
  1102   if (batchIndex >= batches.length) {
  1103     multiLast = {};
  1104     sendOk(command_id);
  1105     return;
  1107   // a set of actions need to be done
  1108   let batch = batches[batchIndex];
  1109   // each action for some finger
  1110   let pack;
  1111   // the touch id for the finger (pack)
  1112   let touchId;
  1113   // command for the finger
  1114   let command;
  1115   // touch that will be created for the finger
  1116   let el;
  1117   let corx;
  1118   let cory;
  1119   let touch;
  1120   let lastTouch;
  1121   let touchIndex;
  1122   let waitTime = 0;
  1123   let maxTime = 0;
  1124   let c;
  1125   batchIndex++;
  1126   // loop through the batch
  1127   for (let i = 0; i < batch.length; i++) {
  1128     pack = batch[i];
  1129     touchId = pack[0];
  1130     command = pack[1];
  1131     switch (command) {
  1132       case 'press':
  1133         el = elementManager.getKnownElement(pack[2], curFrame);
  1134         c = coordinates(el, pack[3], pack[4]);
  1135         touch = createATouch(el, c.x, c.y, touchId);
  1136         multiLast[touchId] = touch;
  1137         touches.push(touch);
  1138         emitMultiEvents('touchstart', touch, touches);
  1139         break;
  1140       case 'release':
  1141         touch = multiLast[touchId];
  1142         // the index of the previous touch for the finger may change in the touches array
  1143         touchIndex = touches.indexOf(touch);
  1144         touches.splice(touchIndex, 1);
  1145         emitMultiEvents('touchend', touch, touches);
  1146         break;
  1147       case 'move':
  1148         el = elementManager.getKnownElement(pack[2], curFrame);
  1149         c = coordinates(el);
  1150         touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
  1151         touchIndex = touches.indexOf(lastTouch);
  1152         touches[touchIndex] = touch;
  1153         multiLast[touchId] = touch;
  1154         emitMultiEvents('touchmove', touch, touches);
  1155         break;
  1156       case 'moveByOffset':
  1157         el = multiLast[touchId].target;
  1158         lastTouch = multiLast[touchId];
  1159         touchIndex = touches.indexOf(lastTouch);
  1160         let doc = el.ownerDocument;
  1161         let win = doc.defaultView;
  1162         // since x and y are relative to the last touch, therefore, it's relative to the position of the last touch
  1163         let clientX = lastTouch.clientX + pack[2],
  1164             clientY = lastTouch.clientY + pack[3];
  1165         let pageX = clientX + win.pageXOffset,
  1166             pageY = clientY + win.pageYOffset;
  1167         let screenX = clientX + win.mozInnerScreenX,
  1168             screenY = clientY + win.mozInnerScreenY;
  1169         touch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
  1170         touches[touchIndex] = touch;
  1171         multiLast[touchId] = touch;
  1172         emitMultiEvents('touchmove', touch, touches);
  1173         break;
  1174       case 'wait':
  1175         if (pack[2] != undefined ) {
  1176           waitTime = pack[2]*1000;
  1177           if (waitTime > maxTime) {
  1178             maxTime = waitTime;
  1181         break;
  1182     }//end of switch block
  1183   }//end of for loop
  1184   if (maxTime != 0) {
  1185     checkTimer.initWithCallback(function(){setDispatch(batches, touches, command_id, batchIndex);}, maxTime, Ci.nsITimer.TYPE_ONE_SHOT);
  1187   else {
  1188     setDispatch(batches, touches, command_id, batchIndex);
  1192 /**
  1193  * Function to start multi-action
  1194  */
  1195 function multiAction(msg) {
  1196   let command_id = msg.json.command_id;
  1197   let args = msg.json.value;
  1198   // maxlen is the longest action chain for one finger
  1199   let maxlen = msg.json.maxlen;
  1200   try {
  1201     // unwrap the original nested array
  1202     let commandArray = elementManager.convertWrappedArguments(args, curFrame);
  1203     let concurrentEvent = [];
  1204     let temp;
  1205     for (let i = 0; i < maxlen; i++) {
  1206       let row = [];
  1207       for (let j = 0; j < commandArray.length; j++) {
  1208         if (commandArray[j][i] != undefined) {
  1209           // add finger id to the front of each action, i.e. [finger_id, action, element]
  1210           temp = commandArray[j][i];
  1211           temp.unshift(j);
  1212           row.push(temp);
  1215       concurrentEvent.push(row);
  1217     // now concurrent event is made of sets where each set contain a list of actions that need to be fired.
  1218     // note: each action belongs to a different finger
  1219     // pendingTouches keeps track of current touches that's on the screen
  1220     let pendingTouches = [];
  1221     setDispatch(concurrentEvent, pendingTouches, command_id);
  1223   catch (e) {
  1224     sendError(e.message, e.code, e.stack, msg.json.command_id);
  1228 /**
  1229  * Navigate to the given URL.  The operation will be performed on the
  1230  * current browser context, and handles the case where we navigate
  1231  * within an iframe.  All other navigation is handled by the server
  1232  * (in chrome space).
  1233  */
  1234 function get(msg) {
  1235   let command_id = msg.json.command_id;
  1237   let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1238   let start = new Date().getTime();
  1239   let end = null;
  1240   function checkLoad() {
  1241     checkTimer.cancel();
  1242     end = new Date().getTime();
  1243     let errorRegex = /about:.+(error)|(blocked)\?/;
  1244     let elapse = end - start;
  1245     if (msg.json.pageTimeout == null || elapse <= msg.json.pageTimeout) {
  1246       if (curFrame.document.readyState == "complete") {
  1247         removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1248         sendOk(command_id);
  1250       else if (curFrame.document.readyState == "interactive" &&
  1251                errorRegex.exec(curFrame.document.baseURI)) {
  1252         removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1253         sendError("Error loading page", 13, null, command_id);
  1255       else {
  1256         checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1259     else {
  1260       removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1261       sendError("Error loading page, timed out (checkLoad)", 21, null,
  1262                 command_id);
  1265   // Prevent DOMContentLoaded events from frames from invoking this
  1266   // code, unless the event is coming from the frame associated with
  1267   // the current window (i.e. someone has used switch_to_frame).
  1268   let onDOMContentLoaded = function onDOMContentLoaded(event) {
  1269     if (!event.originalTarget.defaultView.frameElement ||
  1270         event.originalTarget.defaultView.frameElement == curFrame.frameElement) {
  1271       checkLoad();
  1273   };
  1275   function timerFunc() {
  1276     removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1277     sendError("Error loading page, timed out (onDOMContentLoaded)", 21,
  1278               null, command_id);
  1280   if (msg.json.pageTimeout != null) {
  1281     checkTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
  1283   addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1284   curFrame.location = msg.json.url;
  1287 /**
  1288  * Get URL of the top level browsing context.
  1289  */
  1290 function getCurrentUrl(msg) {
  1291   sendResponse({value: curFrame.location.href}, msg.json.command_id);
  1294 /**
  1295  * Get the current Title of the window
  1296  */
  1297 function getTitle(msg) {
  1298   sendResponse({value: curFrame.top.document.title}, msg.json.command_id);
  1301 /**
  1302  * Get the current page source
  1303  */
  1304 function getPageSource(msg) {
  1305   var XMLSerializer = curFrame.XMLSerializer;
  1306   var pageSource = new XMLSerializer().serializeToString(curFrame.document);
  1307   sendResponse({value: pageSource}, msg.json.command_id);
  1310 /**
  1311  * Go back in history
  1312  */
  1313 function goBack(msg) {
  1314   curFrame.history.back();
  1315   sendOk(msg.json.command_id);
  1318 /**
  1319  * Go forward in history
  1320  */
  1321 function goForward(msg) {
  1322   curFrame.history.forward();
  1323   sendOk(msg.json.command_id);
  1326 /**
  1327  * Refresh the page
  1328  */
  1329 function refresh(msg) {
  1330   let command_id = msg.json.command_id;
  1331   curFrame.location.reload(true);
  1332   let listen = function() {
  1333     removeEventListener("DOMContentLoaded", arguments.callee, false);
  1334     sendOk(command_id);
  1335   };
  1336   addEventListener("DOMContentLoaded", listen, false);
  1339 /**
  1340  * Find an element in the document using requested search strategy
  1341  */
  1342 function findElementContent(msg) {
  1343   let command_id = msg.json.command_id;
  1344   try {
  1345     let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
  1346     let on_error = sendError;
  1347     elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
  1348                         on_success, on_error, false, command_id);
  1350   catch (e) {
  1351     sendError(e.message, e.code, e.stack, command_id);
  1355 /**
  1356  * Find elements in the document using requested search strategy
  1357  */
  1358 function findElementsContent(msg) {
  1359   let command_id = msg.json.command_id;
  1360   try {
  1361     let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
  1362     let on_error = sendError;
  1363     elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
  1364                         on_success, on_error, true, command_id);
  1366   catch (e) {
  1367     sendError(e.message, e.code, e.stack, command_id);
  1371 /**
  1372  * Find and return the active element on the page
  1373  */
  1374 function getActiveElement(msg) {
  1375   let command_id = msg.json.command_id;
  1376   var element = curFrame.document.activeElement;
  1377   var id = elementManager.addToKnownElements(element);
  1378   sendResponse({value: id}, command_id);
  1381 /**
  1382  * Send click event to element
  1383  */
  1384 function clickElement(msg) {
  1385   let command_id = msg.json.command_id;
  1386   let el;
  1387   try {
  1388     el = elementManager.getKnownElement(msg.json.id, curFrame);
  1389     if (checkVisible(el)) {
  1390       if (utils.isElementEnabled(el)) {
  1391         utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView)
  1393       else {
  1394         sendError("Element is not Enabled", 12, null, command_id)
  1397     else {
  1398       sendError("Element is not visible", 11, null, command_id)
  1400     sendOk(command_id);
  1402   catch (e) {
  1403     sendError(e.message, e.code, e.stack, command_id);
  1407 /**
  1408  * Get a given attribute of an element
  1409  */
  1410 function getElementAttribute(msg) {
  1411   let command_id = msg.json.command_id;
  1412   try {
  1413     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1414     sendResponse({value: utils.getElementAttribute(el, msg.json.name)},
  1415                  command_id);
  1417   catch (e) {
  1418     sendError(e.message, e.code, e.stack, command_id);
  1422 /**
  1423  * Get the text of this element. This includes text from child elements.
  1424  */
  1425 function getElementText(msg) {
  1426   let command_id = msg.json.command_id;
  1427   try {
  1428     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1429     sendResponse({value: utils.getElementText(el)}, command_id);
  1431   catch (e) {
  1432     sendError(e.message, e.code, e.stack, command_id);
  1436 /**
  1437  * Get the tag name of an element.
  1438  */
  1439 function getElementTagName(msg) {
  1440   let command_id = msg.json.command_id;
  1441   try {
  1442     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1443     sendResponse({value: el.tagName.toLowerCase()}, command_id);
  1445   catch (e) {
  1446     sendError(e.message, e.code, e.stack, command_id);
  1450 /**
  1451  * Check if element is displayed
  1452  */
  1453 function isElementDisplayed(msg) {
  1454   let command_id = msg.json.command_id;
  1455   try {
  1456     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1457     sendResponse({value: utils.isElementDisplayed(el)}, command_id);
  1459   catch (e) {
  1460     sendError(e.message, e.code, e.stack, command_id);
  1464 /**
  1465  * Return the property of the computed style of an element
  1467  * @param object aRequest
  1468  *               'element' member holds the reference id to
  1469  *               the element that will be checked
  1470  *               'propertyName' is the CSS rule that is being requested
  1471  */
  1472 function getElementValueOfCssProperty(msg){
  1473   let command_id = msg.json.command_id;
  1474   let propertyName = msg.json.propertyName;
  1475   try {
  1476     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1477     sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)},
  1478                  command_id);
  1480   catch (e) {
  1481     sendError(e.message, e.code, e.stack, command_id);
  1485 /**
  1486   * Submit a form on a content page by either using form or element in a form
  1487   * @param object msg
  1488   *               'json' JSON object containing 'id' member of the element
  1489   */
  1490 function submitElement (msg) {
  1491   let command_id = msg.json.command_id;
  1492   try {
  1493     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1494     while (el.parentNode != null && el.tagName.toLowerCase() != 'form') {
  1495       el = el.parentNode;
  1497     if (el.tagName && el.tagName.toLowerCase() == 'form') {
  1498       el.submit();
  1499       sendOk(command_id);
  1501     else {
  1502       sendError("Element is not a form element or in a form", 7, null, command_id);
  1506   catch (e) {
  1507     sendError(e.message, e.code, e.stack, command_id);
  1511 /**
  1512  * Get the size of the element and return it
  1513  */
  1514 function getElementSize(msg){
  1515   let command_id = msg.json.command_id;
  1516   try {
  1517     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1518     let clientRect = el.getBoundingClientRect();
  1519     sendResponse({value: {width: clientRect.width, height: clientRect.height}},
  1520                  command_id);
  1522   catch (e) {
  1523     sendError(e.message, e.code, e.stack, command_id);
  1527 /**
  1528  * Check if element is enabled
  1529  */
  1530 function isElementEnabled(msg) {
  1531   let command_id = msg.json.command_id;
  1532   try {
  1533     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1534     sendResponse({value: utils.isElementEnabled(el)}, command_id);
  1536   catch (e) {
  1537     sendError(e.message, e.code, e.stack, command_id);
  1541 /**
  1542  * Check if element is selected
  1543  */
  1544 function isElementSelected(msg) {
  1545   let command_id = msg.json.command_id;
  1546   try {
  1547     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1548     sendResponse({value: utils.isElementSelected(el)}, command_id);
  1550   catch (e) {
  1551     sendError(e.message, e.code, e.stack, command_id);
  1555 /**
  1556  * Send keys to element
  1557  */
  1558 function sendKeysToElement(msg) {
  1559   let command_id = msg.json.command_id;
  1561   let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1562   if (checkVisible(el)) {
  1563     if (el.mozIsTextField && el.mozIsTextField(false)) {
  1564       var currentTextLength = el.value ? el.value.length : 0;
  1565       el.selectionStart = currentTextLength;
  1566       el.selectionEnd = currentTextLength;
  1568     el.focus();
  1569     var value = msg.json.value.join("");
  1570     let hasShift = null;
  1571     let hasCtrl = null;
  1572     let hasAlt = null;
  1573     let hasMeta = null;
  1574     for (var i = 0; i < value.length; i++) {
  1575       let upper = value.charAt(i).toUpperCase();
  1576       var keyCode = null;
  1577       var c = value.charAt(i);
  1578       switch (c) {
  1579         case '\uE001':
  1580           keyCode = "VK_CANCEL";
  1581           break;
  1582         case '\uE002':
  1583           keyCode = "VK_HELP";
  1584           break;
  1585         case '\uE003':
  1586           keyCode = "VK_BACK_SPACE";
  1587           break;
  1588         case '\uE004':
  1589           keyCode = "VK_TAB";
  1590           break;
  1591         case '\uE005':
  1592           keyCode = "VK_CLEAR";
  1593           break;
  1594         case '\uE006':
  1595         case '\uE007':
  1596           keyCode = "VK_RETURN";
  1597           break;
  1598         case '\uE008':
  1599           keyCode = "VK_SHIFT";
  1600           hasShift = !hasShift;
  1601           break;
  1602         case '\uE009':
  1603           keyCode = "VK_CONTROL";
  1604           controlKey = !controlKey;
  1605           break;
  1606         case '\uE00A':
  1607           keyCode = "VK_ALT";
  1608           altKey = !altKey;
  1609           break;
  1610         case '\uE03D':
  1611           keyCode = "VK_META";
  1612           metaKey = !metaKey;
  1613           break;
  1614         case '\uE00B':
  1615           keyCode = "VK_PAUSE";
  1616           break;
  1617         case '\uE00C':
  1618           keyCode = "VK_ESCAPE";
  1619           break;
  1620         case '\uE00D':
  1621           keyCode = "VK_Space";  // printable
  1622           break;
  1623         case '\uE00E':
  1624           keyCode = "VK_PAGE_UP";
  1625           break;
  1626         case '\uE00F':
  1627           keyCode = "VK_PAGE_DOWN";
  1628           break;
  1629         case '\uE010':
  1630           keyCode = "VK_END";
  1631           break;
  1632         case '\uE011':
  1633           keyCode = "VK_HOME";
  1634           break;
  1635         case '\uE012':
  1636           keyCode = "VK_LEFT";
  1637           break;
  1638         case '\uE013':
  1639           keyCode = "VK_UP";
  1640           break;
  1641         case '\uE014':
  1642           keyCode = "VK_RIGHT";
  1643           break;
  1644         case '\uE015':
  1645           keyCode = "VK_DOWN";
  1646           break;
  1647         case '\uE016':
  1648           keyCode = "VK_INSERT";
  1649           break;
  1650         case '\uE017':
  1651           keyCode = "VK_DELETE";
  1652           break;
  1653         case '\uE018':
  1654           keyCode = "VK_SEMICOLON";
  1655           break;
  1656         case '\uE019':
  1657           keyCode = "VK_EQUALS";
  1658           break;
  1659         case '\uE01A':
  1660           keyCode = "VK_NUMPAD0";
  1661           break;
  1662         case '\uE01B':
  1663           keyCode = "VK_NUMPAD1";
  1664           break;
  1665         case '\uE01C':
  1666           keyCode = "VK_NUMPAD2";
  1667           break;
  1668         case '\uE01D':
  1669           keyCode = "VK_NUMPAD3";
  1670           break;
  1671         case '\uE01E':
  1672           keyCode = "VK_NUMPAD4";
  1673           break;
  1674         case '\uE01F':
  1675           keyCode = "VK_NUMPAD5";
  1676           break;
  1677         case '\uE020':
  1678           keyCode = "VK_NUMPAD6";
  1679           break;
  1680         case '\uE021':
  1681           keyCode = "VK_NUMPAD7";
  1682           break;
  1683         case '\uE022':
  1684           keyCode = "VK_NUMPAD8";
  1685           break;
  1686         case '\uE023':
  1687           keyCode = "VK_NUMPAD9";
  1688           break;
  1689         case '\uE024':
  1690           keyCode = "VK_MULTIPLY";
  1691           break;
  1692         case '\uE025':
  1693           keyCode = "VK_ADD";
  1694           break;
  1695         case '\uE026':
  1696           keyCode = "VK_SEPARATOR";
  1697           break;
  1698         case '\uE027':
  1699           keyCode = "VK_SUBTRACT";
  1700           break;
  1701         case '\uE028':
  1702           keyCode = "VK_DECIMAL";
  1703           break;
  1704         case '\uE029':
  1705           keyCode = "VK_DIVIDE";
  1706           break;
  1707         case '\uE031':
  1708           keyCode = "VK_F1";
  1709           break;
  1710         case '\uE032':
  1711           keyCode = "VK_F2";
  1712           break;
  1713         case '\uE033':
  1714           keyCode = "VK_F3";
  1715           break;
  1716         case '\uE034':
  1717           keyCode = "VK_F4";
  1718           break;
  1719         case '\uE035':
  1720           keyCode = "VK_F5";
  1721           break;
  1722         case '\uE036':
  1723           keyCode = "VK_F6";
  1724           break;
  1725         case '\uE037':
  1726           keyCode = "VK_F7";
  1727           break;
  1728         case '\uE038':
  1729           keyCode = "VK_F8";
  1730           break;
  1731         case '\uE039':
  1732           keyCode = "VK_F9";
  1733           break;
  1734         case '\uE03A':
  1735           keyCode = "VK_F10";
  1736           break;
  1737         case '\uE03B':
  1738           keyCode = "VK_F11";
  1739           break;
  1740         case '\uE03C':
  1741           keyCode = "VK_F12";
  1742           break;
  1744       hasShift = value.charAt(i) == upper;
  1745       utils.synthesizeKey(keyCode || value[i],
  1746                           { shiftKey: hasShift, ctrlKey: hasCtrl, altKey: hasAlt, metaKey: hasMeta },
  1747                           curFrame);
  1748     };
  1749     sendOk(command_id);
  1751   else {
  1752     sendError("Element is not visible", 11, null, command_id)
  1756 /**
  1757  * Get the element's top left-hand corner point.
  1758  */
  1759 function getElementLocation(msg) {
  1760   let command_id = msg.json.command_id;
  1761   try {
  1762     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1763     let rect = el.getBoundingClientRect();
  1765     let location = {};
  1766     location.x = rect.left;
  1767     location.y = rect.top;
  1769     sendResponse({value: location}, command_id);
  1771   catch (e) {
  1772     sendError(e.message, e.code, e.stack, command_id);
  1776 /**
  1777  * Clear the text of an element
  1778  */
  1779 function clearElement(msg) {
  1780   let command_id = msg.json.command_id;
  1781   try {
  1782     let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1783     utils.clearElement(el);
  1784     sendOk(command_id);
  1786   catch (e) {
  1787     sendError(e.message, e.code, e.stack, command_id);
  1791 /**
  1792  * Switch to frame given either the server-assigned element id,
  1793  * its index in window.frames, or the iframe's name or id.
  1794  */
  1795 function switchToFrame(msg) {
  1796   let command_id = msg.json.command_id;
  1797   function checkLoad() {
  1798     let errorRegex = /about:.+(error)|(blocked)\?/;
  1799     if (curFrame.document.readyState == "complete") {
  1800       sendOk(command_id);
  1801       return;
  1803     else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) {
  1804       sendError("Error loading page", 13, null, command_id);
  1805       return;
  1807     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1809   let foundFrame = null;
  1810   let frames = []; //curFrame.document.getElementsByTagName("iframe");
  1811   let parWindow = null; //curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
  1812   // Check of the curFrame reference is dead
  1813   try {
  1814     frames = curFrame.document.getElementsByTagName("iframe");
  1815     //Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
  1816     //parWindow will refer to the iframe above the nested OOP frame.
  1817     parWindow = curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
  1818                       .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
  1819   } catch (e) {
  1820     // We probably have a dead compartment so accessing it is going to make Firefox
  1821     // very upset. Let's now try redirect everything to the top frame even if the
  1822     // user has given us a frame since search doesnt look up.
  1823     msg.json.id = null;
  1824     msg.json.element = null;
  1826   if ((msg.json.id == null) && (msg.json.element == null)) {
  1827     // returning to root frame
  1828     sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
  1830     curFrame = content;
  1831     if(msg.json.focus == true) {
  1832       curFrame.focus();
  1834     sandbox = null;
  1835     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1836     return;
  1838   if (msg.json.element != undefined) {
  1839     if (elementManager.seenItems[msg.json.element] != undefined) {
  1840       let wantedFrame;
  1841       try {
  1842         wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //HTMLIFrameElement
  1844       catch(e) {
  1845         sendError(e.message, e.code, e.stack, command_id);
  1847       for (let i = 0; i < frames.length; i++) {
  1848         // use XPCNativeWrapper to compare elements; see bug 834266
  1849         if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) {
  1850           curFrame = frames[i];
  1851           foundFrame = i;
  1856   if (foundFrame == null) {
  1857     switch(typeof(msg.json.id)) {
  1858       case "string" :
  1859         let foundById = null;
  1860         for (let i = 0; i < frames.length; i++) {
  1861           //give precedence to name
  1862           let frame = frames[i];
  1863           let name = utils.getElementAttribute(frame, 'name');
  1864           let id = utils.getElementAttribute(frame, 'id');
  1865           if (name == msg.json.id) {
  1866             foundFrame = i;
  1867             break;
  1868           } else if ((foundById == null) && (id == msg.json.id)) {
  1869             foundById = i;
  1872         if ((foundFrame == null) && (foundById != null)) {
  1873           foundFrame = foundById;
  1874           curFrame = frames[foundFrame];
  1876         break;
  1877       case "number":
  1878         if (frames[msg.json.id] != undefined) {
  1879           foundFrame = msg.json.id;
  1880           curFrame = frames[foundFrame];
  1882         break;
  1885   if (foundFrame == null) {
  1886     sendError("Unable to locate frame: " + msg.json.id, 8, null, command_id);
  1887     return;
  1890   sandbox = null;
  1892   // send a synchronous message to let the server update the currently active
  1893   // frame element (for getActiveFrame)
  1894   let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
  1895   sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
  1897   if (curFrame.contentWindow == null) {
  1898     // The frame we want to switch to is a remote (out-of-process) frame;
  1899     // notify our parent to handle the switch.
  1900     curFrame = content;
  1901     sendToServer('Marionette:switchToFrame', {frame: foundFrame,
  1902                                               win: parWindow,
  1903                                               command_id: command_id});
  1905   else {
  1906     curFrame = curFrame.contentWindow;
  1907     if(msg.json.focus == true) {
  1908       curFrame.focus();
  1910     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1913  /**
  1914   * Add a cookie to the document
  1915   */
  1916 function addCookie(msg) {
  1917   cookie = msg.json.cookie;
  1919   if (!cookie.expiry) {
  1920     var date = new Date();
  1921     var thePresent = new Date(Date.now());
  1922     date.setYear(thePresent.getFullYear() + 20);
  1923     cookie.expiry = date.getTime() / 1000;  // Stored in seconds.
  1926   if (!cookie.domain) {
  1927     var location = curFrame.document.location;
  1928     cookie.domain = location.hostname;
  1930   else {
  1931     var currLocation = curFrame.location;
  1932     var currDomain = currLocation.host;
  1933     if (currDomain.indexOf(cookie.domain) == -1) {
  1934       sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id);
  1938   // The cookie's domain may include a port. Which is bad. Remove it
  1939   // We'll catch ip6 addresses by mistake. Since no-one uses those
  1940   // this will be okay for now. See Bug 814416
  1941   if (cookie.domain.match(/:\d+$/)) {
  1942     cookie.domain = cookie.domain.replace(/:\d+$/, '');
  1945   var document = curFrame.document;
  1946   if (!document || !document.contentType.match(/html/i)) {
  1947     sendError('You may only set cookies on html documents', 25, null, msg.json.command_id);
  1949   var cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  1950                         getService(Ci.nsICookieManager2);
  1951   cookieManager.add(cookie.domain, cookie.path, cookie.name, cookie.value,
  1952                    cookie.secure, false, false, cookie.expiry);
  1953   sendOk(msg.json.command_id);
  1956 /**
  1957  * Get all cookies for the current domain.
  1958  */
  1959 function getCookies(msg) {
  1960   var toReturn = [];
  1961   var cookies = getVisibleCookies(curFrame.location);
  1962   for (var i = 0; i < cookies.length; i++) {
  1963     var cookie = cookies[i];
  1964     var expires = cookie.expires;
  1965     if (expires == 0) {  // Session cookie, don't return an expiry.
  1966       expires = null;
  1967     } else if (expires == 1) { // Date before epoch time, cap to epoch.
  1968       expires = 0;
  1970     toReturn.push({
  1971       'name': cookie.name,
  1972       'value': cookie.value,
  1973       'path': cookie.path,
  1974       'domain': cookie.host,
  1975       'secure': cookie.isSecure,
  1976       'expiry': expires
  1977     });
  1980   sendResponse({value: toReturn}, msg.json.command_id);
  1983 /**
  1984  * Delete a cookie by name
  1985  */
  1986 function deleteCookie(msg) {
  1987   var toDelete = msg.json.name;
  1988   var cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  1989                         getService(Ci.nsICookieManager);
  1991   var cookies = getVisibleCookies(curFrame.location);
  1992   for (var i = 0; i < cookies.length; i++) {
  1993     var cookie = cookies[i];
  1994     if (cookie.name == toDelete) {
  1995       cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
  1999   sendOk(msg.json.command_id);
  2002 /**
  2003  * Delete all the visibile cookies on a page
  2004  */
  2005 function deleteAllCookies(msg) {
  2006   let cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  2007                         getService(Ci.nsICookieManager);
  2008   let cookies = getVisibleCookies(curFrame.location);
  2009   for (let i = 0; i < cookies.length; i++) {
  2010     let cookie = cookies[i];
  2011     cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
  2013   sendOk(msg.json.command_id);
  2016 /**
  2017  * Get all the visible cookies from a location
  2018  */
  2019 function getVisibleCookies(location) {
  2020   let results = [];
  2021   let currentPath = location.pathname;
  2022   if (!currentPath) currentPath = '/';
  2023   let isForCurrentPath = function(aPath) {
  2024     return currentPath.indexOf(aPath) != -1;
  2027   let cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  2028                         getService(Ci.nsICookieManager);
  2029   let enumerator = cookieManager.enumerator;
  2030   while (enumerator.hasMoreElements()) {
  2031     let cookie = enumerator.getNext().QueryInterface(Ci['nsICookie']);
  2033     // Take the hostname and progressively shorten
  2034     let hostname = location.hostname;
  2035     do {
  2036       if ((cookie.host == '.' + hostname || cookie.host == hostname)
  2037           && isForCurrentPath(cookie.path)) {
  2038           results.push(cookie);
  2039           break;
  2041       hostname = hostname.replace(/^.*?\./, '');
  2042     } while (hostname.indexOf('.') != -1);
  2045   return results;
  2048 function getAppCacheStatus(msg) {
  2049   sendResponse({ value: curFrame.applicationCache.status },
  2050                msg.json.command_id);
  2053 // emulator callbacks
  2054 let _emu_cb_id = 0;
  2055 let _emu_cbs = {};
  2057 function runEmulatorCmd(cmd, callback) {
  2058   if (callback) {
  2059     _emu_cbs[_emu_cb_id] = callback;
  2061   sendAsyncMessage("Marionette:runEmulatorCmd", {emulator_cmd: cmd, id: _emu_cb_id});
  2062   _emu_cb_id += 1;
  2065 function runEmulatorShell(args, callback) {
  2066   if (callback) {
  2067     _emu_cbs[_emu_cb_id] = callback;
  2069   sendAsyncMessage("Marionette:runEmulatorShell", {emulator_shell: args, id: _emu_cb_id});
  2070   _emu_cb_id += 1;
  2073 function emulatorCmdResult(msg) {
  2074   let message = msg.json;
  2075   if (!sandbox) {
  2076     return;
  2078   let cb = _emu_cbs[message.id];
  2079   delete _emu_cbs[message.id];
  2080   if (!cb) {
  2081     return;
  2083   try {
  2084     cb(message.result);
  2086   catch(e) {
  2087     sendError(e.message, e.code, e.stack, -1);
  2088     return;
  2092 function importScript(msg) {
  2093   let command_id = msg.json.command_id;
  2094   let file;
  2095   if (importedScripts.exists()) {
  2096     file = FileUtils.openFileOutputStream(importedScripts,
  2097         FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY);
  2099   else {
  2100     //Note: The permission bits here don't actually get set (bug 804563)
  2101     importedScripts.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE,
  2102                                  parseInt("0666", 8));
  2103     file = FileUtils.openFileOutputStream(importedScripts,
  2104                                           FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
  2105     importedScripts.permissions = parseInt("0666", 8); //actually set permissions
  2107   file.write(msg.json.script, msg.json.script.length);
  2108   file.close();
  2109   sendOk(command_id);
  2112 /**
  2113  * Takes a screen capture of the given web element if <code>id</code>
  2114  * property exists in the message's JSON object, or if null captures
  2115  * the bounding box of the current frame.
  2117  * If given an array of web element references in
  2118  * <code>msg.json.highlights</code>, a red box will be painted around
  2119  * them to highlight their position.
  2120  */
  2121 function takeScreenshot(msg) {
  2122   let node = null;
  2123   if (msg.json.id) {
  2124     try {
  2125       node = elementManager.getKnownElement(msg.json.id, curFrame)
  2127     catch (e) {
  2128       sendResponse(e.message, e.code, e.stack, msg.json.command_id);
  2129       return;
  2132   else {
  2133     node = curFrame;
  2135   let highlights = msg.json.highlights;
  2137   var document = curFrame.document;
  2138   var rect, win, width, height, left, top;
  2139   // node can be either a window or an arbitrary DOM node
  2140   if (node == curFrame) {
  2141     // node is a window
  2142     win = node;
  2143     width = document.body.scrollWidth;
  2144     height = document.body.scrollHeight;
  2145     top = 0;
  2146     left = 0;
  2148   else {
  2149     // node is an arbitrary DOM node
  2150     win = node.ownerDocument.defaultView;
  2151     rect = node.getBoundingClientRect();
  2152     width = rect.width;
  2153     height = rect.height;
  2154     top = rect.top;
  2155     left = rect.left;
  2158   var canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
  2159                                         "canvas");
  2160   canvas.width = width;
  2161   canvas.height = height;
  2162   var ctx = canvas.getContext("2d");
  2163   // Draws the DOM contents of the window to the canvas
  2164   ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
  2166   // This section is for drawing a red rectangle around each element
  2167   // passed in via the highlights array
  2168   if (highlights) {
  2169     ctx.lineWidth = "2";
  2170     ctx.strokeStyle = "red";
  2171     ctx.save();
  2173     for (var i = 0; i < highlights.length; ++i) {
  2174       var elem = elementManager.getKnownElement(highlights[i], curFrame);
  2175       rect = elem.getBoundingClientRect();
  2177       var offsetY = -top;
  2178       var offsetX = -left;
  2180       // Draw the rectangle
  2181       ctx.strokeRect(rect.left + offsetX,
  2182                      rect.top + offsetY,
  2183                      rect.width,
  2184                      rect.height);
  2188   // Return the Base64 encoded string back to the client so that it
  2189   // can save the file to disk if it is required
  2190   var dataUrl = canvas.toDataURL("image/png", "");
  2191   var data = dataUrl.substring(dataUrl.indexOf(",") + 1);
  2192   sendResponse({value: data}, msg.json.command_id);
  2195 // Call register self when we get loaded
  2196 registerSelf();

mercurial