testing/marionette/marionette-listener.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/marionette/marionette-listener.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2196 @@
     1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.10 +
    1.11 +let uuidGen = Cc["@mozilla.org/uuid-generator;1"]
    1.12 +                .getService(Ci.nsIUUIDGenerator);
    1.13 +
    1.14 +let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
    1.15 +               .getService(Ci.mozIJSSubScriptLoader);
    1.16 +
    1.17 +loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
    1.18 +loader.loadSubScript("chrome://marionette/content/marionette-common.js");
    1.19 +Cu.import("chrome://marionette/content/marionette-elements.js");
    1.20 +Cu.import("resource://gre/modules/FileUtils.jsm");
    1.21 +Cu.import("resource://gre/modules/NetUtil.jsm");
    1.22 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.23 +let utils = {};
    1.24 +utils.window = content;
    1.25 +// Load Event/ChromeUtils for use with JS scripts:
    1.26 +loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
    1.27 +loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
    1.28 +loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
    1.29 +loader.loadSubScript("chrome://marionette/content/marionette-sendkeys.js", utils);
    1.30 +
    1.31 +loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js");
    1.32 +loader.loadSubScript("chrome://specialpowers/content/specialpowers.js");
    1.33 +
    1.34 +let marionetteLogObj = new MarionetteLogObj();
    1.35 +
    1.36 +let isB2G = false;
    1.37 +
    1.38 +let marionetteTestName;
    1.39 +let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
    1.40 +                     .getInterface(Ci.nsIDOMWindowUtils);
    1.41 +let listenerId = null; //unique ID of this listener
    1.42 +let curFrame = content;
    1.43 +let previousFrame = null;
    1.44 +let elementManager = new ElementManager([]);
    1.45 +let importedScripts = null;
    1.46 +let inputSource = null;
    1.47 +
    1.48 +// The sandbox we execute test scripts in. Gets lazily created in
    1.49 +// createExecuteContentSandbox().
    1.50 +let sandbox;
    1.51 +
    1.52 +// the unload handler
    1.53 +let onunload;
    1.54 +
    1.55 +// Flag to indicate whether an async script is currently running or not.
    1.56 +let asyncTestRunning = false;
    1.57 +let asyncTestCommandId;
    1.58 +let asyncTestTimeoutId;
    1.59 +
    1.60 +let inactivityTimeoutId = null;
    1.61 +let heartbeatCallback = function () {}; // Called by the simpletest methods.
    1.62 +
    1.63 +let originalOnError;
    1.64 +//timer for doc changes
    1.65 +let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    1.66 +//timer for readystate
    1.67 +let readyStateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    1.68 +// Send move events about this often
    1.69 +let EVENT_INTERVAL = 30; // milliseconds
    1.70 +// For assigning unique ids to all touches
    1.71 +let nextTouchId = 1000;
    1.72 +//Keep track of active Touches
    1.73 +let touchIds = {};
    1.74 +// last touch for each fingerId
    1.75 +let multiLast = {};
    1.76 +let lastCoordinates = null;
    1.77 +let isTap = false;
    1.78 +let scrolling = false;
    1.79 +// whether to send mouse event
    1.80 +let mouseEventsOnly = false;
    1.81 +
    1.82 +Cu.import("resource://gre/modules/Log.jsm");
    1.83 +let logger = Log.repository.getLogger("Marionette");
    1.84 +logger.info("loaded marionette-listener.js");
    1.85 +let modalHandler = function() {
    1.86 +  // This gets called on the system app only since it receives the mozbrowserprompt event
    1.87 +  sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
    1.88 +  let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
    1.89 +  if (isLocal) {
    1.90 +    previousFrame = curFrame;
    1.91 +  }
    1.92 +  curFrame = content;
    1.93 +  sandbox = null;
    1.94 +};
    1.95 +
    1.96 +/**
    1.97 + * Called when listener is first started up.
    1.98 + * The listener sends its unique window ID and its current URI to the actor.
    1.99 + * If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
   1.100 + */
   1.101 +function registerSelf() {
   1.102 +  let msg = {value: winUtil.outerWindowID, href: content.location.href};
   1.103 +  // register will have the ID and a boolean describing if this is the main process or not
   1.104 +  let register = sendSyncMessage("Marionette:register", msg);
   1.105 +
   1.106 +  if (register[0]) {
   1.107 +    listenerId = register[0][0].id;
   1.108 +    // check if we're the main process
   1.109 +    if (register[0][1] == true) {
   1.110 +      addMessageListener("MarionetteMainListener:emitTouchEvent", emitTouchEventForIFrame);
   1.111 +    }
   1.112 +    importedScripts = FileUtils.getDir('TmpD', [], false);
   1.113 +    importedScripts.append('marionetteContentScripts');
   1.114 +    startListeners();
   1.115 +  }
   1.116 +}
   1.117 +
   1.118 +function emitTouchEventForIFrame(message) {
   1.119 +  let message = message.json;
   1.120 +  let frames = curFrame.document.getElementsByTagName("iframe");
   1.121 +  let iframe = frames[message.index];
   1.122 +  let identifier = touchId = nextTouchId++;
   1.123 +  let tabParent = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.tabParent;
   1.124 +  tabParent.injectTouchEvent(message.type, [identifier],
   1.125 +                             [message.clientX], [message.clientY],
   1.126 +                             [message.radiusX], [message.radiusY],
   1.127 +                             [message.rotationAngle], [message.force],
   1.128 +                             1, 0);
   1.129 +}
   1.130 +
   1.131 +/**
   1.132 + * Add a message listener that's tied to our listenerId.
   1.133 + */
   1.134 +function addMessageListenerId(messageName, handler) {
   1.135 +  addMessageListener(messageName + listenerId, handler);
   1.136 +}
   1.137 +
   1.138 +/**
   1.139 + * Remove a message listener that's tied to our listenerId.
   1.140 + */
   1.141 +function removeMessageListenerId(messageName, handler) {
   1.142 +  removeMessageListener(messageName + listenerId, handler);
   1.143 +}
   1.144 +
   1.145 +/**
   1.146 + * Start all message listeners
   1.147 + */
   1.148 +function startListeners() {
   1.149 +  addMessageListenerId("Marionette:newSession", newSession);
   1.150 +  addMessageListenerId("Marionette:executeScript", executeScript);
   1.151 +  addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
   1.152 +  addMessageListenerId("Marionette:executeJSScript", executeJSScript);
   1.153 +  addMessageListenerId("Marionette:singleTap", singleTap);
   1.154 +  addMessageListenerId("Marionette:actionChain", actionChain);
   1.155 +  addMessageListenerId("Marionette:multiAction", multiAction);
   1.156 +  addMessageListenerId("Marionette:get", get);
   1.157 +  addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
   1.158 +  addMessageListenerId("Marionette:getTitle", getTitle);
   1.159 +  addMessageListenerId("Marionette:getPageSource", getPageSource);
   1.160 +  addMessageListenerId("Marionette:goBack", goBack);
   1.161 +  addMessageListenerId("Marionette:goForward", goForward);
   1.162 +  addMessageListenerId("Marionette:refresh", refresh);
   1.163 +  addMessageListenerId("Marionette:findElementContent", findElementContent);
   1.164 +  addMessageListenerId("Marionette:findElementsContent", findElementsContent);
   1.165 +  addMessageListenerId("Marionette:getActiveElement", getActiveElement);
   1.166 +  addMessageListenerId("Marionette:clickElement", clickElement);
   1.167 +  addMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
   1.168 +  addMessageListenerId("Marionette:getElementText", getElementText);
   1.169 +  addMessageListenerId("Marionette:getElementTagName", getElementTagName);
   1.170 +  addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   1.171 +  addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
   1.172 +  addMessageListenerId("Marionette:submitElement", submitElement);
   1.173 +  addMessageListenerId("Marionette:getElementSize", getElementSize);
   1.174 +  addMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   1.175 +  addMessageListenerId("Marionette:isElementSelected", isElementSelected);
   1.176 +  addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   1.177 +  addMessageListenerId("Marionette:getElementLocation", getElementLocation);
   1.178 +  addMessageListenerId("Marionette:clearElement", clearElement);
   1.179 +  addMessageListenerId("Marionette:switchToFrame", switchToFrame);
   1.180 +  addMessageListenerId("Marionette:deleteSession", deleteSession);
   1.181 +  addMessageListenerId("Marionette:sleepSession", sleepSession);
   1.182 +  addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   1.183 +  addMessageListenerId("Marionette:importScript", importScript);
   1.184 +  addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   1.185 +  addMessageListenerId("Marionette:setTestName", setTestName);
   1.186 +  addMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
   1.187 +  addMessageListenerId("Marionette:addCookie", addCookie);
   1.188 +  addMessageListenerId("Marionette:getCookies", getCookies);
   1.189 +  addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
   1.190 +  addMessageListenerId("Marionette:deleteCookie", deleteCookie);
   1.191 +}
   1.192 +
   1.193 +/**
   1.194 + * Used during newSession and restart, called to set up the modal dialog listener in b2g
   1.195 + */
   1.196 +function waitForReady() {
   1.197 +  if (content.document.readyState == 'complete') {
   1.198 +    readyStateTimer.cancel();
   1.199 +    content.addEventListener("mozbrowsershowmodalprompt", modalHandler, false);
   1.200 +    content.addEventListener("unload", waitForReady, false);
   1.201 +  }
   1.202 +  else {
   1.203 +    readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   1.204 +  }
   1.205 +}
   1.206 +
   1.207 +/**
   1.208 + * Called when we start a new session. It registers the
   1.209 + * current environment, and resets all values
   1.210 + */
   1.211 +function newSession(msg) {
   1.212 +  isB2G = msg.json.B2G;
   1.213 +  resetValues();
   1.214 +  if (isB2G) {
   1.215 +    readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   1.216 +    // We have to set correct mouse event source to MOZ_SOURCE_TOUCH
   1.217 +    // to offer a way for event listeners to differentiate
   1.218 +    // events being the result of a physical mouse action.
   1.219 +    // This is especially important for the touch event shim,
   1.220 +    // in order to prevent creating touch event for these fake mouse events.
   1.221 +    inputSource = Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH;
   1.222 +  }
   1.223 +}
   1.224 +
   1.225 +/**
   1.226 + * Puts the current session to sleep, so all listeners are removed except
   1.227 + * for the 'restart' listener. This is used to keep the content listener
   1.228 + * alive for reuse in B2G instead of reloading it each time.
   1.229 + */
   1.230 +function sleepSession(msg) {
   1.231 +  deleteSession();
   1.232 +  addMessageListener("Marionette:restart", restart);
   1.233 +}
   1.234 +
   1.235 +/**
   1.236 + * Restarts all our listeners after this listener was put to sleep
   1.237 + */
   1.238 +function restart(msg) {
   1.239 +  removeMessageListener("Marionette:restart", restart);
   1.240 +  if (isB2G) {
   1.241 +    readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   1.242 +  }
   1.243 +  registerSelf();
   1.244 +}
   1.245 +
   1.246 +/**
   1.247 + * Removes all listeners
   1.248 + */
   1.249 +function deleteSession(msg) {
   1.250 +  removeMessageListenerId("Marionette:newSession", newSession);
   1.251 +  removeMessageListenerId("Marionette:executeScript", executeScript);
   1.252 +  removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
   1.253 +  removeMessageListenerId("Marionette:executeJSScript", executeJSScript);
   1.254 +  removeMessageListenerId("Marionette:singleTap", singleTap);
   1.255 +  removeMessageListenerId("Marionette:actionChain", actionChain);
   1.256 +  removeMessageListenerId("Marionette:multiAction", multiAction);
   1.257 +  removeMessageListenerId("Marionette:get", get);
   1.258 +  removeMessageListenerId("Marionette:getTitle", getTitle);
   1.259 +  removeMessageListenerId("Marionette:getPageSource", getPageSource);
   1.260 +  removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
   1.261 +  removeMessageListenerId("Marionette:goBack", goBack);
   1.262 +  removeMessageListenerId("Marionette:goForward", goForward);
   1.263 +  removeMessageListenerId("Marionette:refresh", refresh);
   1.264 +  removeMessageListenerId("Marionette:findElementContent", findElementContent);
   1.265 +  removeMessageListenerId("Marionette:findElementsContent", findElementsContent);
   1.266 +  removeMessageListenerId("Marionette:getActiveElement", getActiveElement);
   1.267 +  removeMessageListenerId("Marionette:clickElement", clickElement);
   1.268 +  removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
   1.269 +  removeMessageListenerId("Marionette:getElementTagName", getElementTagName);
   1.270 +  removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   1.271 +  removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
   1.272 +  removeMessageListenerId("Marionette:submitElement", submitElement);
   1.273 +  removeMessageListenerId("Marionette:getElementSize", getElementSize);
   1.274 +  removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   1.275 +  removeMessageListenerId("Marionette:isElementSelected", isElementSelected);
   1.276 +  removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   1.277 +  removeMessageListenerId("Marionette:getElementLocation", getElementLocation);
   1.278 +  removeMessageListenerId("Marionette:clearElement", clearElement);
   1.279 +  removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
   1.280 +  removeMessageListenerId("Marionette:deleteSession", deleteSession);
   1.281 +  removeMessageListenerId("Marionette:sleepSession", sleepSession);
   1.282 +  removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   1.283 +  removeMessageListenerId("Marionette:importScript", importScript);
   1.284 +  removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   1.285 +  removeMessageListenerId("Marionette:setTestName", setTestName);
   1.286 +  removeMessageListenerId("Marionette:takeScreenshot", takeScreenshot);
   1.287 +  removeMessageListenerId("Marionette:addCookie", addCookie);
   1.288 +  removeMessageListenerId("Marionette:getCookies", getCookies);
   1.289 +  removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
   1.290 +  removeMessageListenerId("Marionette:deleteCookie", deleteCookie);
   1.291 +  if (isB2G) {
   1.292 +    content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
   1.293 +  }
   1.294 +  elementManager.reset();
   1.295 +  // reset frame to the top-most frame
   1.296 +  curFrame = content;
   1.297 +  curFrame.focus();
   1.298 +  touchIds = {};
   1.299 +}
   1.300 +
   1.301 +/*
   1.302 + * Helper methods
   1.303 + */
   1.304 +
   1.305 +/**
   1.306 + * Generic method to send a message to the server
   1.307 + */
   1.308 +function sendToServer(msg, value, command_id) {
   1.309 +  if (command_id) {
   1.310 +    value.command_id = command_id;
   1.311 +  }
   1.312 +  sendAsyncMessage(msg, value);
   1.313 +}
   1.314 +
   1.315 +/**
   1.316 + * Send response back to server
   1.317 + */
   1.318 +function sendResponse(value, command_id) {
   1.319 +  sendToServer("Marionette:done", value, command_id);
   1.320 +}
   1.321 +
   1.322 +/**
   1.323 + * Send ack back to server
   1.324 + */
   1.325 +function sendOk(command_id) {
   1.326 +  sendToServer("Marionette:ok", {}, command_id);
   1.327 +}
   1.328 +
   1.329 +/**
   1.330 + * Send log message to server
   1.331 + */
   1.332 +function sendLog(msg) {
   1.333 +  sendToServer("Marionette:log", { message: msg });
   1.334 +}
   1.335 +
   1.336 +/**
   1.337 + * Send error message to server
   1.338 + */
   1.339 +function sendError(message, status, trace, command_id) {
   1.340 +  let error_msg = { message: message, status: status, stacktrace: trace };
   1.341 +  sendToServer("Marionette:error", error_msg, command_id);
   1.342 +}
   1.343 +
   1.344 +/**
   1.345 + * Clear test values after completion of test
   1.346 + */
   1.347 +function resetValues() {
   1.348 +  sandbox = null;
   1.349 +  curFrame = content;
   1.350 +  mouseEventsOnly = false;
   1.351 +}
   1.352 +
   1.353 +/**
   1.354 + * Dump a logline to stdout. Prepends logline with a timestamp.
   1.355 + */
   1.356 +function dumpLog(logline) {
   1.357 +  dump(Date.now() + " Marionette: " + logline);
   1.358 +}
   1.359 +
   1.360 +/**
   1.361 + * Check if our context was interrupted
   1.362 + */
   1.363 +function wasInterrupted() {
   1.364 +  if (previousFrame) {
   1.365 +    let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2));
   1.366 +    if (element.id.indexOf("modal-dialog") == -1) {
   1.367 +      return true;
   1.368 +    }
   1.369 +    else {
   1.370 +      return false;
   1.371 +    }
   1.372 +  }
   1.373 +  return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
   1.374 +}
   1.375 +
   1.376 +/*
   1.377 + * Marionette Methods
   1.378 + */
   1.379 +
   1.380 +/**
   1.381 + * Returns a content sandbox that can be used by the execute_foo functions.
   1.382 + */
   1.383 +function createExecuteContentSandbox(aWindow, timeout) {
   1.384 +  let sandbox = new Cu.Sandbox(aWindow, {sandboxPrototype: aWindow});
   1.385 +  sandbox.global = sandbox;
   1.386 +  sandbox.window = aWindow;
   1.387 +  sandbox.document = sandbox.window.document;
   1.388 +  sandbox.navigator = sandbox.window.navigator;
   1.389 +  sandbox.testUtils = utils;
   1.390 +  sandbox.asyncTestCommandId = asyncTestCommandId;
   1.391 +
   1.392 +  let marionette = new Marionette(this, aWindow, "content",
   1.393 +                                  marionetteLogObj, timeout,
   1.394 +                                  heartbeatCallback,
   1.395 +                                  marionetteTestName);
   1.396 +  sandbox.marionette = marionette;
   1.397 +  marionette.exports.forEach(function(fn) {
   1.398 +    try {
   1.399 +      sandbox[fn] = marionette[fn].bind(marionette);
   1.400 +    }
   1.401 +    catch(e) {
   1.402 +      sandbox[fn] = marionette[fn];
   1.403 +    }
   1.404 +  });
   1.405 +
   1.406 +  XPCOMUtils.defineLazyGetter(sandbox, 'SpecialPowers', function() {
   1.407 +    return new SpecialPowers(aWindow);
   1.408 +  });
   1.409 +
   1.410 +  sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) {
   1.411 +    if (commandId == asyncTestCommandId) {
   1.412 +      curFrame.removeEventListener("unload", onunload, false);
   1.413 +      curFrame.clearTimeout(asyncTestTimeoutId);
   1.414 +
   1.415 +      if (inactivityTimeoutId != null) {
   1.416 +        curFrame.clearTimeout(inactivityTimeoutId);
   1.417 +      }
   1.418 +
   1.419 +
   1.420 +      sendSyncMessage("Marionette:shareData",
   1.421 +                      {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   1.422 +      marionetteLogObj.clearLogs();
   1.423 +
   1.424 +      if (status == 0){
   1.425 +        if (Object.keys(_emu_cbs).length) {
   1.426 +          _emu_cbs = {};
   1.427 +          sendError("Emulator callback still pending when finish() called",
   1.428 +                    500, null, commandId);
   1.429 +        }
   1.430 +        else {
   1.431 +          sendResponse({value: elementManager.wrapValue(value), status: status},
   1.432 +                       commandId);
   1.433 +        }
   1.434 +      }
   1.435 +      else {
   1.436 +        sendError(value, status, stack, commandId);
   1.437 +      }
   1.438 +
   1.439 +      asyncTestRunning = false;
   1.440 +      asyncTestTimeoutId = undefined;
   1.441 +      asyncTestCommandId = undefined;
   1.442 +      inactivityTimeoutId = null;
   1.443 +    }
   1.444 +  };
   1.445 +  sandbox.finish = function sandbox_finish() {
   1.446 +    if (asyncTestRunning) {
   1.447 +      sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId);
   1.448 +    } else {
   1.449 +      return marionette.generate_results();
   1.450 +    }
   1.451 +  };
   1.452 +  sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) {
   1.453 +    return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId);
   1.454 +  };
   1.455 +
   1.456 +  return sandbox;
   1.457 +}
   1.458 +
   1.459 +/**
   1.460 + * Execute the given script either as a function body (executeScript)
   1.461 + * or directly (for 'mochitest' like JS Marionette tests)
   1.462 + */
   1.463 +function executeScript(msg, directInject) {
   1.464 +  // Set up inactivity timeout.
   1.465 +  if (msg.json.inactivityTimeout) {
   1.466 +    let setTimer = function() {
   1.467 +        inactivityTimeoutId = curFrame.setTimeout(function() {
   1.468 +        sendError('timed out due to inactivity', 28, null, asyncTestCommandId);
   1.469 +      }, msg.json.inactivityTimeout);
   1.470 +   };
   1.471 +
   1.472 +    setTimer();
   1.473 +    heartbeatCallback = function resetInactivityTimeout() {
   1.474 +      curFrame.clearTimeout(inactivityTimeoutId);
   1.475 +      setTimer();
   1.476 +    };
   1.477 +  }
   1.478 +
   1.479 +  asyncTestCommandId = msg.json.command_id;
   1.480 +  let script = msg.json.script;
   1.481 +
   1.482 +  if (msg.json.newSandbox || !sandbox) {
   1.483 +    sandbox = createExecuteContentSandbox(curFrame,
   1.484 +                                          msg.json.timeout);
   1.485 +    if (!sandbox) {
   1.486 +      sendError("Could not create sandbox!", 500, null, asyncTestCommandId);
   1.487 +      return;
   1.488 +    }
   1.489 +  }
   1.490 +  else {
   1.491 +    sandbox.asyncTestCommandId = asyncTestCommandId;
   1.492 +  }
   1.493 +
   1.494 +  try {
   1.495 +    if (directInject) {
   1.496 +      if (importedScripts.exists()) {
   1.497 +        let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
   1.498 +                      createInstance(Components.interfaces.nsIFileInputStream);
   1.499 +        stream.init(importedScripts, -1, 0, 0);
   1.500 +        let data = NetUtil.readInputStreamToString(stream, stream.available());
   1.501 +        script = data + script;
   1.502 +      }
   1.503 +      let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file" ,0);
   1.504 +      sendSyncMessage("Marionette:shareData",
   1.505 +                      {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   1.506 +      marionetteLogObj.clearLogs();
   1.507 +
   1.508 +      if (res == undefined || res.passed == undefined) {
   1.509 +        sendError("Marionette.finish() not called", 17, null, asyncTestCommandId);
   1.510 +      }
   1.511 +      else {
   1.512 +        sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
   1.513 +      }
   1.514 +    }
   1.515 +    else {
   1.516 +      try {
   1.517 +        sandbox.__marionetteParams = elementManager.convertWrappedArguments(
   1.518 +          msg.json.args, curFrame);
   1.519 +      }
   1.520 +      catch(e) {
   1.521 +        sendError(e.message, e.code, e.stack, asyncTestCommandId);
   1.522 +        return;
   1.523 +      }
   1.524 +
   1.525 +      script = "let __marionetteFunc = function(){" + script + "};" +
   1.526 +                   "__marionetteFunc.apply(null, __marionetteParams);";
   1.527 +      if (importedScripts.exists()) {
   1.528 +        let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
   1.529 +                      createInstance(Components.interfaces.nsIFileInputStream);
   1.530 +        stream.init(importedScripts, -1, 0, 0);
   1.531 +        let data = NetUtil.readInputStreamToString(stream, stream.available());
   1.532 +        script = data + script;
   1.533 +      }
   1.534 +      let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
   1.535 +      sendSyncMessage("Marionette:shareData",
   1.536 +                      {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   1.537 +      marionetteLogObj.clearLogs();
   1.538 +      sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
   1.539 +    }
   1.540 +  }
   1.541 +  catch (e) {
   1.542 +    // 17 = JavascriptException
   1.543 +    let error = createStackMessage(e,
   1.544 +                                   "execute_script",
   1.545 +                                   msg.json.filename,
   1.546 +                                   msg.json.line,
   1.547 +                                   script);
   1.548 +    sendError(error[0], 17, error[1], asyncTestCommandId);
   1.549 +  }
   1.550 +}
   1.551 +
   1.552 +/**
   1.553 + * Sets the test name, used in logging messages.
   1.554 + */
   1.555 +function setTestName(msg) {
   1.556 +  marionetteTestName = msg.json.value;
   1.557 +  sendOk(msg.json.command_id);
   1.558 +}
   1.559 +
   1.560 +/**
   1.561 + * Execute async script
   1.562 + */
   1.563 +function executeAsyncScript(msg) {
   1.564 +  executeWithCallback(msg);
   1.565 +}
   1.566 +
   1.567 +/**
   1.568 + * Execute pure JS test. Handles both async and sync cases.
   1.569 + */
   1.570 +function executeJSScript(msg) {
   1.571 +  if (msg.json.async) {
   1.572 +    executeWithCallback(msg, msg.json.async);
   1.573 +  }
   1.574 +  else {
   1.575 +    executeScript(msg, true);
   1.576 +  }
   1.577 +}
   1.578 +
   1.579 +/**
   1.580 + * This function is used by executeAsync and executeJSScript to execute a script
   1.581 + * in a sandbox.
   1.582 + *
   1.583 + * For executeJSScript, it will return a message only when the finish() method is called.
   1.584 + * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
   1.585 + * method is called, or if it times out.
   1.586 + */
   1.587 +function executeWithCallback(msg, useFinish) {
   1.588 +  // Set up inactivity timeout.
   1.589 +  if (msg.json.inactivityTimeout) {
   1.590 +    let setTimer = function() {
   1.591 +      inactivityTimeoutId = curFrame.setTimeout(function() {
   1.592 +        sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId);
   1.593 +      }, msg.json.inactivityTimeout);
   1.594 +    };
   1.595 +
   1.596 +    setTimer();
   1.597 +    heartbeatCallback = function resetInactivityTimeout() {
   1.598 +      curFrame.clearTimeout(inactivityTimeoutId);
   1.599 +      setTimer();
   1.600 +    };
   1.601 +  }
   1.602 +
   1.603 +  let script = msg.json.script;
   1.604 +  asyncTestCommandId = msg.json.command_id;
   1.605 +
   1.606 +  onunload = function() {
   1.607 +    sendError("unload was called", 17, null, asyncTestCommandId);
   1.608 +  };
   1.609 +  curFrame.addEventListener("unload", onunload, false);
   1.610 +
   1.611 +  if (msg.json.newSandbox || !sandbox) {
   1.612 +    sandbox = createExecuteContentSandbox(curFrame,
   1.613 +                                          msg.json.timeout);
   1.614 +    if (!sandbox) {
   1.615 +      sendError("Could not create sandbox!", 17, null, asyncTestCommandId);
   1.616 +      return;
   1.617 +    }
   1.618 +  }
   1.619 +  else {
   1.620 +    sandbox.asyncTestCommandId = asyncTestCommandId;
   1.621 +  }
   1.622 +  sandbox.tag = script;
   1.623 +
   1.624 +  // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
   1.625 +  // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
   1.626 +  // However Selenium code returns 28, see
   1.627 +  // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
   1.628 +  // We'll stay compatible with the Selenium code.
   1.629 +  asyncTestTimeoutId = curFrame.setTimeout(function() {
   1.630 +    sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId);
   1.631 +  }, msg.json.timeout);
   1.632 +
   1.633 +  originalOnError = curFrame.onerror;
   1.634 +  curFrame.onerror = function errHandler(errMsg, url, line) {
   1.635 +    sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId);
   1.636 +    curFrame.onerror = originalOnError;
   1.637 +  };
   1.638 +
   1.639 +  let scriptSrc;
   1.640 +  if (useFinish) {
   1.641 +    if (msg.json.timeout == null || msg.json.timeout == 0) {
   1.642 +      sendError("Please set a timeout", 21, null, asyncTestCommandId);
   1.643 +    }
   1.644 +    scriptSrc = script;
   1.645 +  }
   1.646 +  else {
   1.647 +    try {
   1.648 +      sandbox.__marionetteParams = elementManager.convertWrappedArguments(
   1.649 +        msg.json.args, curFrame);
   1.650 +    }
   1.651 +    catch(e) {
   1.652 +      sendError(e.message, e.code, e.stack, asyncTestCommandId);
   1.653 +      return;
   1.654 +    }
   1.655 +
   1.656 +    scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
   1.657 +                "let __marionetteFunc = function() { " + script + "};" +
   1.658 +                "__marionetteFunc.apply(null, __marionetteParams); ";
   1.659 +  }
   1.660 +
   1.661 +  try {
   1.662 +    asyncTestRunning = true;
   1.663 +    if (importedScripts.exists()) {
   1.664 +      let stream = Cc["@mozilla.org/network/file-input-stream;1"].
   1.665 +                      createInstance(Ci.nsIFileInputStream);
   1.666 +      stream.init(importedScripts, -1, 0, 0);
   1.667 +      let data = NetUtil.readInputStreamToString(stream, stream.available());
   1.668 +      scriptSrc = data + scriptSrc;
   1.669 +    }
   1.670 +    Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0);
   1.671 +  } catch (e) {
   1.672 +    // 17 = JavascriptException
   1.673 +    let error = createStackMessage(e,
   1.674 +                                   "execute_async_script",
   1.675 +                                   msg.json.filename,
   1.676 +                                   msg.json.line,
   1.677 +                                   scriptSrc);
   1.678 +    sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId);
   1.679 +  }
   1.680 +}
   1.681 +
   1.682 +/**
   1.683 + * This function creates a touch event given a touch type and a touch
   1.684 + */
   1.685 +function emitTouchEvent(type, touch) {
   1.686 +  if (!wasInterrupted()) {
   1.687 +    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";
   1.688 +    dumpLog(loggingInfo);
   1.689 +    var docShell = curFrame.document.defaultView.
   1.690 +                   QueryInterface(Components.interfaces.nsIInterfaceRequestor).
   1.691 +                   getInterface(Components.interfaces.nsIWebNavigation).
   1.692 +                   QueryInterface(Components.interfaces.nsIDocShell);
   1.693 +    if (docShell.asyncPanZoomEnabled && scrolling) {
   1.694 +      // if we're in APZ and we're scrolling, we must use injectTouchEvent to dispatch our touchmove events
   1.695 +      let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId");
   1.696 +      // only call emitTouchEventForIFrame if we're inside an iframe.
   1.697 +      if (index != null) {
   1.698 +        sendSyncMessage("Marionette:emitTouchEvent", {index: index, type: type, id: touch.identifier,
   1.699 +                                                      clientX: touch.clientX, clientY: touch.clientY,
   1.700 +                                                      radiusX: touch.radiusX, radiusY: touch.radiusY,
   1.701 +                                                      rotation: touch.rotationAngle, force: touch.force});
   1.702 +        return;
   1.703 +      }
   1.704 +    }
   1.705 +    // we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
   1.706 +    /*
   1.707 +    Disabled per bug 888303
   1.708 +    marionetteLogObj.log(loggingInfo, "TRACE");
   1.709 +    sendSyncMessage("Marionette:shareData",
   1.710 +                    {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   1.711 +    marionetteLogObj.clearLogs();
   1.712 +    */
   1.713 +    let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
   1.714 +    domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
   1.715 +  }
   1.716 +}
   1.717 +
   1.718 +/**
   1.719 + * This function emit mouse event
   1.720 + *   @param: doc is the current document
   1.721 + *           type is the type of event to dispatch
   1.722 + *           clickCount is the number of clicks, button notes the mouse button
   1.723 + *           elClientX and elClientY are the coordinates of the mouse relative to the viewport
   1.724 + */
   1.725 +function emitMouseEvent(doc, type, elClientX, elClientY, clickCount, button) {
   1.726 +  if (!wasInterrupted()) {
   1.727 +    let loggingInfo = "emitting Mouse event of type " + type + " at coordinates (" + elClientX + ", " + elClientY + ") relative to the viewport";
   1.728 +    dumpLog(loggingInfo);
   1.729 +    /*
   1.730 +    Disabled per bug 888303
   1.731 +    marionetteLogObj.log(loggingInfo, "TRACE");
   1.732 +    sendSyncMessage("Marionette:shareData",
   1.733 +                    {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
   1.734 +    marionetteLogObj.clearLogs();
   1.735 +    */
   1.736 +    let win = doc.defaultView;
   1.737 +    let domUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
   1.738 +    domUtils.sendMouseEvent(type, elClientX, elClientY, button || 0, clickCount || 1, 0, false, 0, inputSource);
   1.739 +  }
   1.740 +}
   1.741 +
   1.742 +/**
   1.743 + * Helper function that perform a mouse tap
   1.744 + */
   1.745 +function mousetap(doc, x, y) {
   1.746 +  emitMouseEvent(doc, 'mousemove', x, y);
   1.747 +  emitMouseEvent(doc, 'mousedown', x, y);
   1.748 +  emitMouseEvent(doc, 'mouseup', x, y);
   1.749 +}
   1.750 +
   1.751 +
   1.752 +/**
   1.753 + * This function generates a pair of coordinates relative to the viewport given a
   1.754 + * target element and coordinates relative to that element's top-left corner.
   1.755 + * @param 'x', and 'y' are the relative to the target.
   1.756 + *        If they are not specified, then the center of the target is used.
   1.757 + */
   1.758 +function coordinates(target, x, y) {
   1.759 +  let box = target.getBoundingClientRect();
   1.760 +  if (x == null) {
   1.761 +    x = box.width / 2;
   1.762 +  }
   1.763 +  if (y == null) {
   1.764 +    y = box.height / 2;
   1.765 +  }
   1.766 +  let coords = {};
   1.767 +  coords.x = box.left + x;
   1.768 +  coords.y = box.top + y;
   1.769 +  return coords;
   1.770 +}
   1.771 +
   1.772 +/**
   1.773 + * This function returns if the element is in viewport
   1.774 + */
   1.775 +function elementInViewport(el) {
   1.776 +  let rect = el.getBoundingClientRect();
   1.777 +  let viewPort = {top: curFrame.pageYOffset,
   1.778 +                  left: curFrame.pageXOffset,
   1.779 +                  bottom: (curFrame.pageYOffset + curFrame.innerHeight),
   1.780 +                  right:(curFrame.pageXOffset + curFrame.innerWidth)};
   1.781 +  return (viewPort.left <= rect.right + curFrame.pageXOffset &&
   1.782 +          rect.left + curFrame.pageXOffset <= viewPort.right &&
   1.783 +          viewPort.top <= rect.bottom + curFrame.pageYOffset &&
   1.784 +          rect.top + curFrame.pageYOffset <= viewPort.bottom);
   1.785 +}
   1.786 +
   1.787 +/**
   1.788 + * This function throws the visibility of the element error
   1.789 + */
   1.790 +function checkVisible(el) {
   1.791 +  //check if the element is visible
   1.792 +  let visible = utils.isElementDisplayed(el);
   1.793 +  if (!visible) {
   1.794 +    return false;
   1.795 +  }
   1.796 +  if (el.tagName.toLowerCase() === 'body') {
   1.797 +    return true;
   1.798 +  }
   1.799 +  if (!elementInViewport(el)) {
   1.800 +    //check if scroll function exist. If so, call it.
   1.801 +    if (el.scrollIntoView) {
   1.802 +      el.scrollIntoView(false);
   1.803 +      if (!elementInViewport(el)) {
   1.804 +        return false;
   1.805 +      }
   1.806 +    }
   1.807 +    else {
   1.808 +      return false;
   1.809 +    }
   1.810 +  }
   1.811 +  return true;
   1.812 +}
   1.813 +
   1.814 +//x and y are coordinates relative to the viewport
   1.815 +function generateEvents(type, x, y, touchId, target) {
   1.816 +  lastCoordinates = [x, y];
   1.817 +  let doc = curFrame.document;
   1.818 +  switch (type) {
   1.819 +    case 'tap':
   1.820 +      if (mouseEventsOnly) {
   1.821 +        mousetap(target.ownerDocument, x, y);
   1.822 +      }
   1.823 +      else {
   1.824 +        let touchId = nextTouchId++;
   1.825 +        let touch = createATouch(target, x, y, touchId);
   1.826 +        emitTouchEvent('touchstart', touch);
   1.827 +        emitTouchEvent('touchend', touch);
   1.828 +        mousetap(target.ownerDocument, x, y);
   1.829 +      }
   1.830 +      lastCoordinates = null;
   1.831 +      break;
   1.832 +    case 'press':
   1.833 +      isTap = true;
   1.834 +      if (mouseEventsOnly) {
   1.835 +        emitMouseEvent(doc, 'mousemove', x, y);
   1.836 +        emitMouseEvent(doc, 'mousedown', x, y);
   1.837 +      }
   1.838 +      else {
   1.839 +        let touchId = nextTouchId++;
   1.840 +        let touch = createATouch(target, x, y, touchId);
   1.841 +        emitTouchEvent('touchstart', touch);
   1.842 +        touchIds[touchId] = touch;
   1.843 +        return touchId;
   1.844 +      }
   1.845 +      break;
   1.846 +    case 'release':
   1.847 +      if (mouseEventsOnly) {
   1.848 +        emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]);
   1.849 +      }
   1.850 +      else {
   1.851 +        let touch = touchIds[touchId];
   1.852 +        touch = createATouch(touch.target, lastCoordinates[0], lastCoordinates[1], touchId);
   1.853 +        emitTouchEvent('touchend', touch);
   1.854 +        if (isTap) {
   1.855 +          mousetap(touch.target.ownerDocument, touch.clientX, touch.clientY);
   1.856 +        }
   1.857 +        delete touchIds[touchId];
   1.858 +      }
   1.859 +      isTap = false;
   1.860 +      lastCoordinates = null;
   1.861 +      break;
   1.862 +    case 'cancel':
   1.863 +      isTap = false;
   1.864 +      if (mouseEventsOnly) {
   1.865 +        emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]);
   1.866 +      }
   1.867 +      else {
   1.868 +        emitTouchEvent('touchcancel', touchIds[touchId]);
   1.869 +        delete touchIds[touchId];
   1.870 +      }
   1.871 +      lastCoordinates = null;
   1.872 +      break;
   1.873 +    case 'move':
   1.874 +      isTap = false;
   1.875 +      if (mouseEventsOnly) {
   1.876 +        emitMouseEvent(doc, 'mousemove', x, y);
   1.877 +      }
   1.878 +      else {
   1.879 +        touch = createATouch(touchIds[touchId].target, x, y, touchId);
   1.880 +        touchIds[touchId] = touch;
   1.881 +        emitTouchEvent('touchmove', touch);
   1.882 +      }
   1.883 +      break;
   1.884 +    case 'contextmenu':
   1.885 +      isTap = false;
   1.886 +      let event = curFrame.document.createEvent('HTMLEvents');
   1.887 +      event.initEvent('contextmenu', true, true);
   1.888 +      if (mouseEventsOnly) {
   1.889 +        target = doc.elementFromPoint(lastCoordinates[0], lastCoordinates[1]);
   1.890 +      }
   1.891 +      else {
   1.892 +        target = touchIds[touchId].target;
   1.893 +      }
   1.894 +      target.dispatchEvent(event);
   1.895 +      break;
   1.896 +    default:
   1.897 +      throw {message:"Unknown event type: " + type, code: 500, stack:null};
   1.898 +  }
   1.899 +  if (wasInterrupted()) {
   1.900 +    if (previousFrame) {
   1.901 +      //if previousFrame is set, then we're in a single process environment
   1.902 +      curFrame = previousFrame;
   1.903 +      previousFrame = null;
   1.904 +      sandbox = null;
   1.905 +    }
   1.906 +    else {
   1.907 +      //else we're in OOP environment, so we'll switch to the original OOP frame
   1.908 +      sendSyncMessage("Marionette:switchToModalOrigin");
   1.909 +    }
   1.910 +    sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
   1.911 +  }
   1.912 +}
   1.913 +
   1.914 +/**
   1.915 + * Function that perform a single tap
   1.916 + */
   1.917 +function singleTap(msg) {
   1.918 +  let command_id = msg.json.command_id;
   1.919 +  try {
   1.920 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
   1.921 +    // after this block, the element will be scrolled into view
   1.922 +    if (!checkVisible(el)) {
   1.923 +       sendError("Element is not currently visible and may not be manipulated", 11, null, command_id);
   1.924 +       return;
   1.925 +    }
   1.926 +    if (!curFrame.document.createTouch) {
   1.927 +      mouseEventsOnly = true;
   1.928 +    }
   1.929 +    let c = coordinates(el, msg.json.corx, msg.json.cory);
   1.930 +    generateEvents('tap', c.x, c.y, null, el);
   1.931 +    sendOk(msg.json.command_id);
   1.932 +  }
   1.933 +  catch (e) {
   1.934 +    sendError(e.message, e.code, e.stack, msg.json.command_id);
   1.935 +  }
   1.936 +}
   1.937 +
   1.938 +/**
   1.939 + * Function to create a touch based on the element
   1.940 + * corx and cory are relative to the viewport, id is the touchId
   1.941 + */
   1.942 +function createATouch(el, corx, cory, touchId) {
   1.943 +  let doc = el.ownerDocument;
   1.944 +  let win = doc.defaultView;
   1.945 +  let clientX = corx;
   1.946 +  let clientY = cory;
   1.947 +  let pageX = clientX + win.pageXOffset,
   1.948 +      pageY = clientY + win.pageYOffset;
   1.949 +  let screenX = clientX + win.mozInnerScreenX,
   1.950 +      screenY = clientY + win.mozInnerScreenY;
   1.951 +  let atouch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
   1.952 +  return atouch;
   1.953 +}
   1.954 +
   1.955 +/**
   1.956 + * Function to emit touch events for each finger. e.g. finger=[['press', id], ['wait', 5], ['release']]
   1.957 + * touchId represents the finger id, i keeps track of the current action of the chain
   1.958 + */
   1.959 +function actions(chain, touchId, command_id, i) {
   1.960 +  if (typeof i === "undefined") {
   1.961 +    i = 0;
   1.962 +  }
   1.963 +  if (i == chain.length) {
   1.964 +    sendResponse({value: touchId}, command_id);
   1.965 +    return;
   1.966 +  }
   1.967 +  let pack = chain[i];
   1.968 +  let command = pack[0];
   1.969 +  let el;
   1.970 +  let c;
   1.971 +  i++;
   1.972 +  if (command != 'press') {
   1.973 +    //if mouseEventsOnly, then touchIds isn't used
   1.974 +    if (!(touchId in touchIds) && !mouseEventsOnly) {
   1.975 +      sendError("Element has not been pressed", 500, null, command_id);
   1.976 +      return;
   1.977 +    }
   1.978 +  }
   1.979 +  switch(command) {
   1.980 +    case 'press':
   1.981 +      if (lastCoordinates) {
   1.982 +        generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId);
   1.983 +        sendError("Invalid Command: press cannot follow an active touch event", 500, null, command_id);
   1.984 +        return;
   1.985 +      }
   1.986 +      // look ahead to check if we're scrolling. Needed for APZ touch dispatching.
   1.987 +      if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
   1.988 +        scrolling = true;
   1.989 +      }
   1.990 +      el = elementManager.getKnownElement(pack[1], curFrame);
   1.991 +      c = coordinates(el, pack[2], pack[3]);
   1.992 +      touchId = generateEvents('press', c.x, c.y, null, el);
   1.993 +      actions(chain, touchId, command_id, i);
   1.994 +      break;
   1.995 +    case 'release':
   1.996 +      generateEvents('release', lastCoordinates[0], lastCoordinates[1], touchId);
   1.997 +      actions(chain, null, command_id, i);
   1.998 +      scrolling =  false;
   1.999 +      break;
  1.1000 +    case 'move':
  1.1001 +      el = elementManager.getKnownElement(pack[1], curFrame);
  1.1002 +      c = coordinates(el);
  1.1003 +      generateEvents('move', c.x, c.y, touchId);
  1.1004 +      actions(chain, touchId, command_id, i);
  1.1005 +      break;
  1.1006 +    case 'moveByOffset':
  1.1007 +      generateEvents('move', lastCoordinates[0] + pack[1], lastCoordinates[1] + pack[2], touchId);
  1.1008 +      actions(chain, touchId, command_id, i);
  1.1009 +      break;
  1.1010 +    case 'wait':
  1.1011 +      if (pack[1] != null ) {
  1.1012 +        let time = pack[1]*1000;
  1.1013 +        // standard waiting time to fire contextmenu
  1.1014 +        let standard = 750;
  1.1015 +        try {
  1.1016 +          standard = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
  1.1017 +        }
  1.1018 +        catch (e){}
  1.1019 +        if (time >= standard && isTap) {
  1.1020 +            chain.splice(i, 0, ['longPress'], ['wait', (time-standard)/1000]);
  1.1021 +            time = standard;
  1.1022 +        }
  1.1023 +        checkTimer.initWithCallback(function(){actions(chain, touchId, command_id, i);}, time, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1024 +      }
  1.1025 +      else {
  1.1026 +        actions(chain, touchId, command_id, i);
  1.1027 +      }
  1.1028 +      break;
  1.1029 +    case 'cancel':
  1.1030 +      generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId);
  1.1031 +      actions(chain, touchId, command_id, i);
  1.1032 +      scrolling = false;
  1.1033 +      break;
  1.1034 +    case 'longPress':
  1.1035 +      generateEvents('contextmenu', lastCoordinates[0], lastCoordinates[1], touchId);
  1.1036 +      actions(chain, touchId, command_id, i);
  1.1037 +      break;
  1.1038 +  }
  1.1039 +}
  1.1040 +
  1.1041 +/**
  1.1042 + * Function to start action chain on one finger
  1.1043 + */
  1.1044 +function actionChain(msg) {
  1.1045 +  let command_id = msg.json.command_id;
  1.1046 +  let args = msg.json.chain;
  1.1047 +  let touchId = msg.json.nextId;
  1.1048 +  try {
  1.1049 +    let commandArray = elementManager.convertWrappedArguments(args, curFrame);
  1.1050 +    // loop the action array [ ['press', id], ['move', id], ['release', id] ]
  1.1051 +    if (touchId == null) {
  1.1052 +      touchId = nextTouchId++;
  1.1053 +    }
  1.1054 +    if (!curFrame.document.createTouch) {
  1.1055 +      mouseEventsOnly = true;
  1.1056 +    }
  1.1057 +    actions(commandArray, touchId, command_id);
  1.1058 +  }
  1.1059 +  catch (e) {
  1.1060 +    sendError(e.message, e.code, e.stack, msg.json.command_id);
  1.1061 +  }
  1.1062 +}
  1.1063 +
  1.1064 +/**
  1.1065 + * Function to emit touch events which allow multi touch on the screen
  1.1066 + * @param type represents the type of event, touch represents the current touch,touches are all pending touches
  1.1067 + */
  1.1068 +function emitMultiEvents(type, touch, touches) {
  1.1069 +  let target = touch.target;
  1.1070 +  let doc = target.ownerDocument;
  1.1071 +  let win = doc.defaultView;
  1.1072 +  // touches that are in the same document
  1.1073 +  let documentTouches = doc.createTouchList(touches.filter(function(t) {
  1.1074 +    return ((t.target.ownerDocument === doc) && (type != 'touchcancel'));
  1.1075 +  }));
  1.1076 +  // touches on the same target
  1.1077 +  let targetTouches = doc.createTouchList(touches.filter(function(t) {
  1.1078 +    return ((t.target === target) && ((type != 'touchcancel') || (type != 'touchend')));
  1.1079 +  }));
  1.1080 +  // Create changed touches
  1.1081 +  let changedTouches = doc.createTouchList(touch);
  1.1082 +  // Create the event object
  1.1083 +  let event = doc.createEvent('TouchEvent');
  1.1084 +  event.initTouchEvent(type,
  1.1085 +                       true,
  1.1086 +                       true,
  1.1087 +                       win,
  1.1088 +                       0,
  1.1089 +                       false, false, false, false,
  1.1090 +                       documentTouches,
  1.1091 +                       targetTouches,
  1.1092 +                       changedTouches);
  1.1093 +  target.dispatchEvent(event);
  1.1094 +}
  1.1095 +
  1.1096 +/**
  1.1097 + * Function to dispatch one set of actions
  1.1098 + * @param touches represents all pending touches, batchIndex represents the batch we are dispatching right now
  1.1099 + */
  1.1100 +function setDispatch(batches, touches, command_id, batchIndex) {
  1.1101 +  if (typeof batchIndex === "undefined") {
  1.1102 +    batchIndex = 0;
  1.1103 +  }
  1.1104 +  // check if all the sets have been fired
  1.1105 +  if (batchIndex >= batches.length) {
  1.1106 +    multiLast = {};
  1.1107 +    sendOk(command_id);
  1.1108 +    return;
  1.1109 +  }
  1.1110 +  // a set of actions need to be done
  1.1111 +  let batch = batches[batchIndex];
  1.1112 +  // each action for some finger
  1.1113 +  let pack;
  1.1114 +  // the touch id for the finger (pack)
  1.1115 +  let touchId;
  1.1116 +  // command for the finger
  1.1117 +  let command;
  1.1118 +  // touch that will be created for the finger
  1.1119 +  let el;
  1.1120 +  let corx;
  1.1121 +  let cory;
  1.1122 +  let touch;
  1.1123 +  let lastTouch;
  1.1124 +  let touchIndex;
  1.1125 +  let waitTime = 0;
  1.1126 +  let maxTime = 0;
  1.1127 +  let c;
  1.1128 +  batchIndex++;
  1.1129 +  // loop through the batch
  1.1130 +  for (let i = 0; i < batch.length; i++) {
  1.1131 +    pack = batch[i];
  1.1132 +    touchId = pack[0];
  1.1133 +    command = pack[1];
  1.1134 +    switch (command) {
  1.1135 +      case 'press':
  1.1136 +        el = elementManager.getKnownElement(pack[2], curFrame);
  1.1137 +        c = coordinates(el, pack[3], pack[4]);
  1.1138 +        touch = createATouch(el, c.x, c.y, touchId);
  1.1139 +        multiLast[touchId] = touch;
  1.1140 +        touches.push(touch);
  1.1141 +        emitMultiEvents('touchstart', touch, touches);
  1.1142 +        break;
  1.1143 +      case 'release':
  1.1144 +        touch = multiLast[touchId];
  1.1145 +        // the index of the previous touch for the finger may change in the touches array
  1.1146 +        touchIndex = touches.indexOf(touch);
  1.1147 +        touches.splice(touchIndex, 1);
  1.1148 +        emitMultiEvents('touchend', touch, touches);
  1.1149 +        break;
  1.1150 +      case 'move':
  1.1151 +        el = elementManager.getKnownElement(pack[2], curFrame);
  1.1152 +        c = coordinates(el);
  1.1153 +        touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
  1.1154 +        touchIndex = touches.indexOf(lastTouch);
  1.1155 +        touches[touchIndex] = touch;
  1.1156 +        multiLast[touchId] = touch;
  1.1157 +        emitMultiEvents('touchmove', touch, touches);
  1.1158 +        break;
  1.1159 +      case 'moveByOffset':
  1.1160 +        el = multiLast[touchId].target;
  1.1161 +        lastTouch = multiLast[touchId];
  1.1162 +        touchIndex = touches.indexOf(lastTouch);
  1.1163 +        let doc = el.ownerDocument;
  1.1164 +        let win = doc.defaultView;
  1.1165 +        // since x and y are relative to the last touch, therefore, it's relative to the position of the last touch
  1.1166 +        let clientX = lastTouch.clientX + pack[2],
  1.1167 +            clientY = lastTouch.clientY + pack[3];
  1.1168 +        let pageX = clientX + win.pageXOffset,
  1.1169 +            pageY = clientY + win.pageYOffset;
  1.1170 +        let screenX = clientX + win.mozInnerScreenX,
  1.1171 +            screenY = clientY + win.mozInnerScreenY;
  1.1172 +        touch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
  1.1173 +        touches[touchIndex] = touch;
  1.1174 +        multiLast[touchId] = touch;
  1.1175 +        emitMultiEvents('touchmove', touch, touches);
  1.1176 +        break;
  1.1177 +      case 'wait':
  1.1178 +        if (pack[2] != undefined ) {
  1.1179 +          waitTime = pack[2]*1000;
  1.1180 +          if (waitTime > maxTime) {
  1.1181 +            maxTime = waitTime;
  1.1182 +          }
  1.1183 +        }
  1.1184 +        break;
  1.1185 +    }//end of switch block
  1.1186 +  }//end of for loop
  1.1187 +  if (maxTime != 0) {
  1.1188 +    checkTimer.initWithCallback(function(){setDispatch(batches, touches, command_id, batchIndex);}, maxTime, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1189 +  }
  1.1190 +  else {
  1.1191 +    setDispatch(batches, touches, command_id, batchIndex);
  1.1192 +  }
  1.1193 +}
  1.1194 +
  1.1195 +/**
  1.1196 + * Function to start multi-action
  1.1197 + */
  1.1198 +function multiAction(msg) {
  1.1199 +  let command_id = msg.json.command_id;
  1.1200 +  let args = msg.json.value;
  1.1201 +  // maxlen is the longest action chain for one finger
  1.1202 +  let maxlen = msg.json.maxlen;
  1.1203 +  try {
  1.1204 +    // unwrap the original nested array
  1.1205 +    let commandArray = elementManager.convertWrappedArguments(args, curFrame);
  1.1206 +    let concurrentEvent = [];
  1.1207 +    let temp;
  1.1208 +    for (let i = 0; i < maxlen; i++) {
  1.1209 +      let row = [];
  1.1210 +      for (let j = 0; j < commandArray.length; j++) {
  1.1211 +        if (commandArray[j][i] != undefined) {
  1.1212 +          // add finger id to the front of each action, i.e. [finger_id, action, element]
  1.1213 +          temp = commandArray[j][i];
  1.1214 +          temp.unshift(j);
  1.1215 +          row.push(temp);
  1.1216 +        }
  1.1217 +      }
  1.1218 +      concurrentEvent.push(row);
  1.1219 +    }
  1.1220 +    // now concurrent event is made of sets where each set contain a list of actions that need to be fired.
  1.1221 +    // note: each action belongs to a different finger
  1.1222 +    // pendingTouches keeps track of current touches that's on the screen
  1.1223 +    let pendingTouches = [];
  1.1224 +    setDispatch(concurrentEvent, pendingTouches, command_id);
  1.1225 +  }
  1.1226 +  catch (e) {
  1.1227 +    sendError(e.message, e.code, e.stack, msg.json.command_id);
  1.1228 +  }
  1.1229 +}
  1.1230 +
  1.1231 +/**
  1.1232 + * Navigate to the given URL.  The operation will be performed on the
  1.1233 + * current browser context, and handles the case where we navigate
  1.1234 + * within an iframe.  All other navigation is handled by the server
  1.1235 + * (in chrome space).
  1.1236 + */
  1.1237 +function get(msg) {
  1.1238 +  let command_id = msg.json.command_id;
  1.1239 +
  1.1240 +  let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1.1241 +  let start = new Date().getTime();
  1.1242 +  let end = null;
  1.1243 +  function checkLoad() {
  1.1244 +    checkTimer.cancel();
  1.1245 +    end = new Date().getTime();
  1.1246 +    let errorRegex = /about:.+(error)|(blocked)\?/;
  1.1247 +    let elapse = end - start;
  1.1248 +    if (msg.json.pageTimeout == null || elapse <= msg.json.pageTimeout) {
  1.1249 +      if (curFrame.document.readyState == "complete") {
  1.1250 +        removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1.1251 +        sendOk(command_id);
  1.1252 +      }
  1.1253 +      else if (curFrame.document.readyState == "interactive" &&
  1.1254 +               errorRegex.exec(curFrame.document.baseURI)) {
  1.1255 +        removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1.1256 +        sendError("Error loading page", 13, null, command_id);
  1.1257 +      }
  1.1258 +      else {
  1.1259 +        checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1260 +      }
  1.1261 +    }
  1.1262 +    else {
  1.1263 +      removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1.1264 +      sendError("Error loading page, timed out (checkLoad)", 21, null,
  1.1265 +                command_id);
  1.1266 +    }
  1.1267 +  }
  1.1268 +  // Prevent DOMContentLoaded events from frames from invoking this
  1.1269 +  // code, unless the event is coming from the frame associated with
  1.1270 +  // the current window (i.e. someone has used switch_to_frame).
  1.1271 +  let onDOMContentLoaded = function onDOMContentLoaded(event) {
  1.1272 +    if (!event.originalTarget.defaultView.frameElement ||
  1.1273 +        event.originalTarget.defaultView.frameElement == curFrame.frameElement) {
  1.1274 +      checkLoad();
  1.1275 +    }
  1.1276 +  };
  1.1277 +
  1.1278 +  function timerFunc() {
  1.1279 +    removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1.1280 +    sendError("Error loading page, timed out (onDOMContentLoaded)", 21,
  1.1281 +              null, command_id);
  1.1282 +  }
  1.1283 +  if (msg.json.pageTimeout != null) {
  1.1284 +    checkTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1285 +  }
  1.1286 +  addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
  1.1287 +  curFrame.location = msg.json.url;
  1.1288 +}
  1.1289 +
  1.1290 +/**
  1.1291 + * Get URL of the top level browsing context.
  1.1292 + */
  1.1293 +function getCurrentUrl(msg) {
  1.1294 +  sendResponse({value: curFrame.location.href}, msg.json.command_id);
  1.1295 +}
  1.1296 +
  1.1297 +/**
  1.1298 + * Get the current Title of the window
  1.1299 + */
  1.1300 +function getTitle(msg) {
  1.1301 +  sendResponse({value: curFrame.top.document.title}, msg.json.command_id);
  1.1302 +}
  1.1303 +
  1.1304 +/**
  1.1305 + * Get the current page source
  1.1306 + */
  1.1307 +function getPageSource(msg) {
  1.1308 +  var XMLSerializer = curFrame.XMLSerializer;
  1.1309 +  var pageSource = new XMLSerializer().serializeToString(curFrame.document);
  1.1310 +  sendResponse({value: pageSource}, msg.json.command_id);
  1.1311 +}
  1.1312 +
  1.1313 +/**
  1.1314 + * Go back in history
  1.1315 + */
  1.1316 +function goBack(msg) {
  1.1317 +  curFrame.history.back();
  1.1318 +  sendOk(msg.json.command_id);
  1.1319 +}
  1.1320 +
  1.1321 +/**
  1.1322 + * Go forward in history
  1.1323 + */
  1.1324 +function goForward(msg) {
  1.1325 +  curFrame.history.forward();
  1.1326 +  sendOk(msg.json.command_id);
  1.1327 +}
  1.1328 +
  1.1329 +/**
  1.1330 + * Refresh the page
  1.1331 + */
  1.1332 +function refresh(msg) {
  1.1333 +  let command_id = msg.json.command_id;
  1.1334 +  curFrame.location.reload(true);
  1.1335 +  let listen = function() {
  1.1336 +    removeEventListener("DOMContentLoaded", arguments.callee, false);
  1.1337 +    sendOk(command_id);
  1.1338 +  };
  1.1339 +  addEventListener("DOMContentLoaded", listen, false);
  1.1340 +}
  1.1341 +
  1.1342 +/**
  1.1343 + * Find an element in the document using requested search strategy
  1.1344 + */
  1.1345 +function findElementContent(msg) {
  1.1346 +  let command_id = msg.json.command_id;
  1.1347 +  try {
  1.1348 +    let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
  1.1349 +    let on_error = sendError;
  1.1350 +    elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
  1.1351 +                        on_success, on_error, false, command_id);
  1.1352 +  }
  1.1353 +  catch (e) {
  1.1354 +    sendError(e.message, e.code, e.stack, command_id);
  1.1355 +  }
  1.1356 +}
  1.1357 +
  1.1358 +/**
  1.1359 + * Find elements in the document using requested search strategy
  1.1360 + */
  1.1361 +function findElementsContent(msg) {
  1.1362 +  let command_id = msg.json.command_id;
  1.1363 +  try {
  1.1364 +    let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
  1.1365 +    let on_error = sendError;
  1.1366 +    elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
  1.1367 +                        on_success, on_error, true, command_id);
  1.1368 +  }
  1.1369 +  catch (e) {
  1.1370 +    sendError(e.message, e.code, e.stack, command_id);
  1.1371 +  }
  1.1372 +}
  1.1373 +
  1.1374 +/**
  1.1375 + * Find and return the active element on the page
  1.1376 + */
  1.1377 +function getActiveElement(msg) {
  1.1378 +  let command_id = msg.json.command_id;
  1.1379 +  var element = curFrame.document.activeElement;
  1.1380 +  var id = elementManager.addToKnownElements(element);
  1.1381 +  sendResponse({value: id}, command_id);
  1.1382 +}
  1.1383 +
  1.1384 +/**
  1.1385 + * Send click event to element
  1.1386 + */
  1.1387 +function clickElement(msg) {
  1.1388 +  let command_id = msg.json.command_id;
  1.1389 +  let el;
  1.1390 +  try {
  1.1391 +    el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1392 +    if (checkVisible(el)) {
  1.1393 +      if (utils.isElementEnabled(el)) {
  1.1394 +        utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView)
  1.1395 +      }
  1.1396 +      else {
  1.1397 +        sendError("Element is not Enabled", 12, null, command_id)
  1.1398 +      }
  1.1399 +    }
  1.1400 +    else {
  1.1401 +      sendError("Element is not visible", 11, null, command_id)
  1.1402 +    }
  1.1403 +    sendOk(command_id);
  1.1404 +  }
  1.1405 +  catch (e) {
  1.1406 +    sendError(e.message, e.code, e.stack, command_id);
  1.1407 +  }
  1.1408 +}
  1.1409 +
  1.1410 +/**
  1.1411 + * Get a given attribute of an element
  1.1412 + */
  1.1413 +function getElementAttribute(msg) {
  1.1414 +  let command_id = msg.json.command_id;
  1.1415 +  try {
  1.1416 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1417 +    sendResponse({value: utils.getElementAttribute(el, msg.json.name)},
  1.1418 +                 command_id);
  1.1419 +  }
  1.1420 +  catch (e) {
  1.1421 +    sendError(e.message, e.code, e.stack, command_id);
  1.1422 +  }
  1.1423 +}
  1.1424 +
  1.1425 +/**
  1.1426 + * Get the text of this element. This includes text from child elements.
  1.1427 + */
  1.1428 +function getElementText(msg) {
  1.1429 +  let command_id = msg.json.command_id;
  1.1430 +  try {
  1.1431 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1432 +    sendResponse({value: utils.getElementText(el)}, command_id);
  1.1433 +  }
  1.1434 +  catch (e) {
  1.1435 +    sendError(e.message, e.code, e.stack, command_id);
  1.1436 +  }
  1.1437 +}
  1.1438 +
  1.1439 +/**
  1.1440 + * Get the tag name of an element.
  1.1441 + */
  1.1442 +function getElementTagName(msg) {
  1.1443 +  let command_id = msg.json.command_id;
  1.1444 +  try {
  1.1445 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1446 +    sendResponse({value: el.tagName.toLowerCase()}, command_id);
  1.1447 +  }
  1.1448 +  catch (e) {
  1.1449 +    sendError(e.message, e.code, e.stack, command_id);
  1.1450 +  }
  1.1451 +}
  1.1452 +
  1.1453 +/**
  1.1454 + * Check if element is displayed
  1.1455 + */
  1.1456 +function isElementDisplayed(msg) {
  1.1457 +  let command_id = msg.json.command_id;
  1.1458 +  try {
  1.1459 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1460 +    sendResponse({value: utils.isElementDisplayed(el)}, command_id);
  1.1461 +  }
  1.1462 +  catch (e) {
  1.1463 +    sendError(e.message, e.code, e.stack, command_id);
  1.1464 +  }
  1.1465 +}
  1.1466 +
  1.1467 +/**
  1.1468 + * Return the property of the computed style of an element
  1.1469 + *
  1.1470 + * @param object aRequest
  1.1471 + *               'element' member holds the reference id to
  1.1472 + *               the element that will be checked
  1.1473 + *               'propertyName' is the CSS rule that is being requested
  1.1474 + */
  1.1475 +function getElementValueOfCssProperty(msg){
  1.1476 +  let command_id = msg.json.command_id;
  1.1477 +  let propertyName = msg.json.propertyName;
  1.1478 +  try {
  1.1479 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1480 +    sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)},
  1.1481 +                 command_id);
  1.1482 +  }
  1.1483 +  catch (e) {
  1.1484 +    sendError(e.message, e.code, e.stack, command_id);
  1.1485 +  }
  1.1486 +}
  1.1487 +
  1.1488 +/**
  1.1489 +  * Submit a form on a content page by either using form or element in a form
  1.1490 +  * @param object msg
  1.1491 +  *               'json' JSON object containing 'id' member of the element
  1.1492 +  */
  1.1493 +function submitElement (msg) {
  1.1494 +  let command_id = msg.json.command_id;
  1.1495 +  try {
  1.1496 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1497 +    while (el.parentNode != null && el.tagName.toLowerCase() != 'form') {
  1.1498 +      el = el.parentNode;
  1.1499 +    }
  1.1500 +    if (el.tagName && el.tagName.toLowerCase() == 'form') {
  1.1501 +      el.submit();
  1.1502 +      sendOk(command_id);
  1.1503 +    }
  1.1504 +    else {
  1.1505 +      sendError("Element is not a form element or in a form", 7, null, command_id);
  1.1506 +    }
  1.1507 +
  1.1508 +  }
  1.1509 +  catch (e) {
  1.1510 +    sendError(e.message, e.code, e.stack, command_id);
  1.1511 +  }
  1.1512 +}
  1.1513 +
  1.1514 +/**
  1.1515 + * Get the size of the element and return it
  1.1516 + */
  1.1517 +function getElementSize(msg){
  1.1518 +  let command_id = msg.json.command_id;
  1.1519 +  try {
  1.1520 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1521 +    let clientRect = el.getBoundingClientRect();
  1.1522 +    sendResponse({value: {width: clientRect.width, height: clientRect.height}},
  1.1523 +                 command_id);
  1.1524 +  }
  1.1525 +  catch (e) {
  1.1526 +    sendError(e.message, e.code, e.stack, command_id);
  1.1527 +  }
  1.1528 +}
  1.1529 +
  1.1530 +/**
  1.1531 + * Check if element is enabled
  1.1532 + */
  1.1533 +function isElementEnabled(msg) {
  1.1534 +  let command_id = msg.json.command_id;
  1.1535 +  try {
  1.1536 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1537 +    sendResponse({value: utils.isElementEnabled(el)}, command_id);
  1.1538 +  }
  1.1539 +  catch (e) {
  1.1540 +    sendError(e.message, e.code, e.stack, command_id);
  1.1541 +  }
  1.1542 +}
  1.1543 +
  1.1544 +/**
  1.1545 + * Check if element is selected
  1.1546 + */
  1.1547 +function isElementSelected(msg) {
  1.1548 +  let command_id = msg.json.command_id;
  1.1549 +  try {
  1.1550 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1551 +    sendResponse({value: utils.isElementSelected(el)}, command_id);
  1.1552 +  }
  1.1553 +  catch (e) {
  1.1554 +    sendError(e.message, e.code, e.stack, command_id);
  1.1555 +  }
  1.1556 +}
  1.1557 +
  1.1558 +/**
  1.1559 + * Send keys to element
  1.1560 + */
  1.1561 +function sendKeysToElement(msg) {
  1.1562 +  let command_id = msg.json.command_id;
  1.1563 +
  1.1564 +  let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1565 +  if (checkVisible(el)) {
  1.1566 +    if (el.mozIsTextField && el.mozIsTextField(false)) {
  1.1567 +      var currentTextLength = el.value ? el.value.length : 0;
  1.1568 +      el.selectionStart = currentTextLength;
  1.1569 +      el.selectionEnd = currentTextLength;
  1.1570 +    }
  1.1571 +    el.focus();
  1.1572 +    var value = msg.json.value.join("");
  1.1573 +    let hasShift = null;
  1.1574 +    let hasCtrl = null;
  1.1575 +    let hasAlt = null;
  1.1576 +    let hasMeta = null;
  1.1577 +    for (var i = 0; i < value.length; i++) {
  1.1578 +      let upper = value.charAt(i).toUpperCase();
  1.1579 +      var keyCode = null;
  1.1580 +      var c = value.charAt(i);
  1.1581 +      switch (c) {
  1.1582 +        case '\uE001':
  1.1583 +          keyCode = "VK_CANCEL";
  1.1584 +          break;
  1.1585 +        case '\uE002':
  1.1586 +          keyCode = "VK_HELP";
  1.1587 +          break;
  1.1588 +        case '\uE003':
  1.1589 +          keyCode = "VK_BACK_SPACE";
  1.1590 +          break;
  1.1591 +        case '\uE004':
  1.1592 +          keyCode = "VK_TAB";
  1.1593 +          break;
  1.1594 +        case '\uE005':
  1.1595 +          keyCode = "VK_CLEAR";
  1.1596 +          break;
  1.1597 +        case '\uE006':
  1.1598 +        case '\uE007':
  1.1599 +          keyCode = "VK_RETURN";
  1.1600 +          break;
  1.1601 +        case '\uE008':
  1.1602 +          keyCode = "VK_SHIFT";
  1.1603 +          hasShift = !hasShift;
  1.1604 +          break;
  1.1605 +        case '\uE009':
  1.1606 +          keyCode = "VK_CONTROL";
  1.1607 +          controlKey = !controlKey;
  1.1608 +          break;
  1.1609 +        case '\uE00A':
  1.1610 +          keyCode = "VK_ALT";
  1.1611 +          altKey = !altKey;
  1.1612 +          break;
  1.1613 +        case '\uE03D':
  1.1614 +          keyCode = "VK_META";
  1.1615 +          metaKey = !metaKey;
  1.1616 +          break;
  1.1617 +        case '\uE00B':
  1.1618 +          keyCode = "VK_PAUSE";
  1.1619 +          break;
  1.1620 +        case '\uE00C':
  1.1621 +          keyCode = "VK_ESCAPE";
  1.1622 +          break;
  1.1623 +        case '\uE00D':
  1.1624 +          keyCode = "VK_Space";  // printable
  1.1625 +          break;
  1.1626 +        case '\uE00E':
  1.1627 +          keyCode = "VK_PAGE_UP";
  1.1628 +          break;
  1.1629 +        case '\uE00F':
  1.1630 +          keyCode = "VK_PAGE_DOWN";
  1.1631 +          break;
  1.1632 +        case '\uE010':
  1.1633 +          keyCode = "VK_END";
  1.1634 +          break;
  1.1635 +        case '\uE011':
  1.1636 +          keyCode = "VK_HOME";
  1.1637 +          break;
  1.1638 +        case '\uE012':
  1.1639 +          keyCode = "VK_LEFT";
  1.1640 +          break;
  1.1641 +        case '\uE013':
  1.1642 +          keyCode = "VK_UP";
  1.1643 +          break;
  1.1644 +        case '\uE014':
  1.1645 +          keyCode = "VK_RIGHT";
  1.1646 +          break;
  1.1647 +        case '\uE015':
  1.1648 +          keyCode = "VK_DOWN";
  1.1649 +          break;
  1.1650 +        case '\uE016':
  1.1651 +          keyCode = "VK_INSERT";
  1.1652 +          break;
  1.1653 +        case '\uE017':
  1.1654 +          keyCode = "VK_DELETE";
  1.1655 +          break;
  1.1656 +        case '\uE018':
  1.1657 +          keyCode = "VK_SEMICOLON";
  1.1658 +          break;
  1.1659 +        case '\uE019':
  1.1660 +          keyCode = "VK_EQUALS";
  1.1661 +          break;
  1.1662 +        case '\uE01A':
  1.1663 +          keyCode = "VK_NUMPAD0";
  1.1664 +          break;
  1.1665 +        case '\uE01B':
  1.1666 +          keyCode = "VK_NUMPAD1";
  1.1667 +          break;
  1.1668 +        case '\uE01C':
  1.1669 +          keyCode = "VK_NUMPAD2";
  1.1670 +          break;
  1.1671 +        case '\uE01D':
  1.1672 +          keyCode = "VK_NUMPAD3";
  1.1673 +          break;
  1.1674 +        case '\uE01E':
  1.1675 +          keyCode = "VK_NUMPAD4";
  1.1676 +          break;
  1.1677 +        case '\uE01F':
  1.1678 +          keyCode = "VK_NUMPAD5";
  1.1679 +          break;
  1.1680 +        case '\uE020':
  1.1681 +          keyCode = "VK_NUMPAD6";
  1.1682 +          break;
  1.1683 +        case '\uE021':
  1.1684 +          keyCode = "VK_NUMPAD7";
  1.1685 +          break;
  1.1686 +        case '\uE022':
  1.1687 +          keyCode = "VK_NUMPAD8";
  1.1688 +          break;
  1.1689 +        case '\uE023':
  1.1690 +          keyCode = "VK_NUMPAD9";
  1.1691 +          break;
  1.1692 +        case '\uE024':
  1.1693 +          keyCode = "VK_MULTIPLY";
  1.1694 +          break;
  1.1695 +        case '\uE025':
  1.1696 +          keyCode = "VK_ADD";
  1.1697 +          break;
  1.1698 +        case '\uE026':
  1.1699 +          keyCode = "VK_SEPARATOR";
  1.1700 +          break;
  1.1701 +        case '\uE027':
  1.1702 +          keyCode = "VK_SUBTRACT";
  1.1703 +          break;
  1.1704 +        case '\uE028':
  1.1705 +          keyCode = "VK_DECIMAL";
  1.1706 +          break;
  1.1707 +        case '\uE029':
  1.1708 +          keyCode = "VK_DIVIDE";
  1.1709 +          break;
  1.1710 +        case '\uE031':
  1.1711 +          keyCode = "VK_F1";
  1.1712 +          break;
  1.1713 +        case '\uE032':
  1.1714 +          keyCode = "VK_F2";
  1.1715 +          break;
  1.1716 +        case '\uE033':
  1.1717 +          keyCode = "VK_F3";
  1.1718 +          break;
  1.1719 +        case '\uE034':
  1.1720 +          keyCode = "VK_F4";
  1.1721 +          break;
  1.1722 +        case '\uE035':
  1.1723 +          keyCode = "VK_F5";
  1.1724 +          break;
  1.1725 +        case '\uE036':
  1.1726 +          keyCode = "VK_F6";
  1.1727 +          break;
  1.1728 +        case '\uE037':
  1.1729 +          keyCode = "VK_F7";
  1.1730 +          break;
  1.1731 +        case '\uE038':
  1.1732 +          keyCode = "VK_F8";
  1.1733 +          break;
  1.1734 +        case '\uE039':
  1.1735 +          keyCode = "VK_F9";
  1.1736 +          break;
  1.1737 +        case '\uE03A':
  1.1738 +          keyCode = "VK_F10";
  1.1739 +          break;
  1.1740 +        case '\uE03B':
  1.1741 +          keyCode = "VK_F11";
  1.1742 +          break;
  1.1743 +        case '\uE03C':
  1.1744 +          keyCode = "VK_F12";
  1.1745 +          break;
  1.1746 +      }
  1.1747 +      hasShift = value.charAt(i) == upper;
  1.1748 +      utils.synthesizeKey(keyCode || value[i],
  1.1749 +                          { shiftKey: hasShift, ctrlKey: hasCtrl, altKey: hasAlt, metaKey: hasMeta },
  1.1750 +                          curFrame);
  1.1751 +    };
  1.1752 +    sendOk(command_id);
  1.1753 +  }
  1.1754 +  else {
  1.1755 +    sendError("Element is not visible", 11, null, command_id)
  1.1756 +  }
  1.1757 +}
  1.1758 +
  1.1759 +/**
  1.1760 + * Get the element's top left-hand corner point.
  1.1761 + */
  1.1762 +function getElementLocation(msg) {
  1.1763 +  let command_id = msg.json.command_id;
  1.1764 +  try {
  1.1765 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1766 +    let rect = el.getBoundingClientRect();
  1.1767 +
  1.1768 +    let location = {};
  1.1769 +    location.x = rect.left;
  1.1770 +    location.y = rect.top;
  1.1771 +
  1.1772 +    sendResponse({value: location}, command_id);
  1.1773 +  }
  1.1774 +  catch (e) {
  1.1775 +    sendError(e.message, e.code, e.stack, command_id);
  1.1776 +  }
  1.1777 +}
  1.1778 +
  1.1779 +/**
  1.1780 + * Clear the text of an element
  1.1781 + */
  1.1782 +function clearElement(msg) {
  1.1783 +  let command_id = msg.json.command_id;
  1.1784 +  try {
  1.1785 +    let el = elementManager.getKnownElement(msg.json.id, curFrame);
  1.1786 +    utils.clearElement(el);
  1.1787 +    sendOk(command_id);
  1.1788 +  }
  1.1789 +  catch (e) {
  1.1790 +    sendError(e.message, e.code, e.stack, command_id);
  1.1791 +  }
  1.1792 +}
  1.1793 +
  1.1794 +/**
  1.1795 + * Switch to frame given either the server-assigned element id,
  1.1796 + * its index in window.frames, or the iframe's name or id.
  1.1797 + */
  1.1798 +function switchToFrame(msg) {
  1.1799 +  let command_id = msg.json.command_id;
  1.1800 +  function checkLoad() {
  1.1801 +    let errorRegex = /about:.+(error)|(blocked)\?/;
  1.1802 +    if (curFrame.document.readyState == "complete") {
  1.1803 +      sendOk(command_id);
  1.1804 +      return;
  1.1805 +    }
  1.1806 +    else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) {
  1.1807 +      sendError("Error loading page", 13, null, command_id);
  1.1808 +      return;
  1.1809 +    }
  1.1810 +    checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1811 +  }
  1.1812 +  let foundFrame = null;
  1.1813 +  let frames = []; //curFrame.document.getElementsByTagName("iframe");
  1.1814 +  let parWindow = null; //curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
  1.1815 +  // Check of the curFrame reference is dead
  1.1816 +  try {
  1.1817 +    frames = curFrame.document.getElementsByTagName("iframe");
  1.1818 +    //Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
  1.1819 +    //parWindow will refer to the iframe above the nested OOP frame.
  1.1820 +    parWindow = curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
  1.1821 +                      .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
  1.1822 +  } catch (e) {
  1.1823 +    // We probably have a dead compartment so accessing it is going to make Firefox
  1.1824 +    // very upset. Let's now try redirect everything to the top frame even if the
  1.1825 +    // user has given us a frame since search doesnt look up.
  1.1826 +    msg.json.id = null;
  1.1827 +    msg.json.element = null;
  1.1828 +  }
  1.1829 +  if ((msg.json.id == null) && (msg.json.element == null)) {
  1.1830 +    // returning to root frame
  1.1831 +    sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
  1.1832 +
  1.1833 +    curFrame = content;
  1.1834 +    if(msg.json.focus == true) {
  1.1835 +      curFrame.focus();
  1.1836 +    }
  1.1837 +    sandbox = null;
  1.1838 +    checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1839 +    return;
  1.1840 +  }
  1.1841 +  if (msg.json.element != undefined) {
  1.1842 +    if (elementManager.seenItems[msg.json.element] != undefined) {
  1.1843 +      let wantedFrame;
  1.1844 +      try {
  1.1845 +        wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //HTMLIFrameElement
  1.1846 +      }
  1.1847 +      catch(e) {
  1.1848 +        sendError(e.message, e.code, e.stack, command_id);
  1.1849 +      }
  1.1850 +      for (let i = 0; i < frames.length; i++) {
  1.1851 +        // use XPCNativeWrapper to compare elements; see bug 834266
  1.1852 +        if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) {
  1.1853 +          curFrame = frames[i];
  1.1854 +          foundFrame = i;
  1.1855 +        }
  1.1856 +      }
  1.1857 +    }
  1.1858 +  }
  1.1859 +  if (foundFrame == null) {
  1.1860 +    switch(typeof(msg.json.id)) {
  1.1861 +      case "string" :
  1.1862 +        let foundById = null;
  1.1863 +        for (let i = 0; i < frames.length; i++) {
  1.1864 +          //give precedence to name
  1.1865 +          let frame = frames[i];
  1.1866 +          let name = utils.getElementAttribute(frame, 'name');
  1.1867 +          let id = utils.getElementAttribute(frame, 'id');
  1.1868 +          if (name == msg.json.id) {
  1.1869 +            foundFrame = i;
  1.1870 +            break;
  1.1871 +          } else if ((foundById == null) && (id == msg.json.id)) {
  1.1872 +            foundById = i;
  1.1873 +          }
  1.1874 +        }
  1.1875 +        if ((foundFrame == null) && (foundById != null)) {
  1.1876 +          foundFrame = foundById;
  1.1877 +          curFrame = frames[foundFrame];
  1.1878 +        }
  1.1879 +        break;
  1.1880 +      case "number":
  1.1881 +        if (frames[msg.json.id] != undefined) {
  1.1882 +          foundFrame = msg.json.id;
  1.1883 +          curFrame = frames[foundFrame];
  1.1884 +        }
  1.1885 +        break;
  1.1886 +    }
  1.1887 +  }
  1.1888 +  if (foundFrame == null) {
  1.1889 +    sendError("Unable to locate frame: " + msg.json.id, 8, null, command_id);
  1.1890 +    return;
  1.1891 +  }
  1.1892 +
  1.1893 +  sandbox = null;
  1.1894 +
  1.1895 +  // send a synchronous message to let the server update the currently active
  1.1896 +  // frame element (for getActiveFrame)
  1.1897 +  let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
  1.1898 +  sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
  1.1899 +
  1.1900 +  if (curFrame.contentWindow == null) {
  1.1901 +    // The frame we want to switch to is a remote (out-of-process) frame;
  1.1902 +    // notify our parent to handle the switch.
  1.1903 +    curFrame = content;
  1.1904 +    sendToServer('Marionette:switchToFrame', {frame: foundFrame,
  1.1905 +                                              win: parWindow,
  1.1906 +                                              command_id: command_id});
  1.1907 +  }
  1.1908 +  else {
  1.1909 +    curFrame = curFrame.contentWindow;
  1.1910 +    if(msg.json.focus == true) {
  1.1911 +      curFrame.focus();
  1.1912 +    }
  1.1913 +    checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1.1914 +  }
  1.1915 +}
  1.1916 + /**
  1.1917 +  * Add a cookie to the document
  1.1918 +  */
  1.1919 +function addCookie(msg) {
  1.1920 +  cookie = msg.json.cookie;
  1.1921 +
  1.1922 +  if (!cookie.expiry) {
  1.1923 +    var date = new Date();
  1.1924 +    var thePresent = new Date(Date.now());
  1.1925 +    date.setYear(thePresent.getFullYear() + 20);
  1.1926 +    cookie.expiry = date.getTime() / 1000;  // Stored in seconds.
  1.1927 +  }
  1.1928 +
  1.1929 +  if (!cookie.domain) {
  1.1930 +    var location = curFrame.document.location;
  1.1931 +    cookie.domain = location.hostname;
  1.1932 +  }
  1.1933 +  else {
  1.1934 +    var currLocation = curFrame.location;
  1.1935 +    var currDomain = currLocation.host;
  1.1936 +    if (currDomain.indexOf(cookie.domain) == -1) {
  1.1937 +      sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id);
  1.1938 +    }
  1.1939 +  }
  1.1940 +
  1.1941 +  // The cookie's domain may include a port. Which is bad. Remove it
  1.1942 +  // We'll catch ip6 addresses by mistake. Since no-one uses those
  1.1943 +  // this will be okay for now. See Bug 814416
  1.1944 +  if (cookie.domain.match(/:\d+$/)) {
  1.1945 +    cookie.domain = cookie.domain.replace(/:\d+$/, '');
  1.1946 +  }
  1.1947 +
  1.1948 +  var document = curFrame.document;
  1.1949 +  if (!document || !document.contentType.match(/html/i)) {
  1.1950 +    sendError('You may only set cookies on html documents', 25, null, msg.json.command_id);
  1.1951 +  }
  1.1952 +  var cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  1.1953 +                        getService(Ci.nsICookieManager2);
  1.1954 +  cookieManager.add(cookie.domain, cookie.path, cookie.name, cookie.value,
  1.1955 +                   cookie.secure, false, false, cookie.expiry);
  1.1956 +  sendOk(msg.json.command_id);
  1.1957 +}
  1.1958 +
  1.1959 +/**
  1.1960 + * Get all cookies for the current domain.
  1.1961 + */
  1.1962 +function getCookies(msg) {
  1.1963 +  var toReturn = [];
  1.1964 +  var cookies = getVisibleCookies(curFrame.location);
  1.1965 +  for (var i = 0; i < cookies.length; i++) {
  1.1966 +    var cookie = cookies[i];
  1.1967 +    var expires = cookie.expires;
  1.1968 +    if (expires == 0) {  // Session cookie, don't return an expiry.
  1.1969 +      expires = null;
  1.1970 +    } else if (expires == 1) { // Date before epoch time, cap to epoch.
  1.1971 +      expires = 0;
  1.1972 +    }
  1.1973 +    toReturn.push({
  1.1974 +      'name': cookie.name,
  1.1975 +      'value': cookie.value,
  1.1976 +      'path': cookie.path,
  1.1977 +      'domain': cookie.host,
  1.1978 +      'secure': cookie.isSecure,
  1.1979 +      'expiry': expires
  1.1980 +    });
  1.1981 +  }
  1.1982 +
  1.1983 +  sendResponse({value: toReturn}, msg.json.command_id);
  1.1984 +}
  1.1985 +
  1.1986 +/**
  1.1987 + * Delete a cookie by name
  1.1988 + */
  1.1989 +function deleteCookie(msg) {
  1.1990 +  var toDelete = msg.json.name;
  1.1991 +  var cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  1.1992 +                        getService(Ci.nsICookieManager);
  1.1993 +
  1.1994 +  var cookies = getVisibleCookies(curFrame.location);
  1.1995 +  for (var i = 0; i < cookies.length; i++) {
  1.1996 +    var cookie = cookies[i];
  1.1997 +    if (cookie.name == toDelete) {
  1.1998 +      cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
  1.1999 +    }
  1.2000 +  }
  1.2001 +
  1.2002 +  sendOk(msg.json.command_id);
  1.2003 +}
  1.2004 +
  1.2005 +/**
  1.2006 + * Delete all the visibile cookies on a page
  1.2007 + */
  1.2008 +function deleteAllCookies(msg) {
  1.2009 +  let cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  1.2010 +                        getService(Ci.nsICookieManager);
  1.2011 +  let cookies = getVisibleCookies(curFrame.location);
  1.2012 +  for (let i = 0; i < cookies.length; i++) {
  1.2013 +    let cookie = cookies[i];
  1.2014 +    cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
  1.2015 +  }
  1.2016 +  sendOk(msg.json.command_id);
  1.2017 +}
  1.2018 +
  1.2019 +/**
  1.2020 + * Get all the visible cookies from a location
  1.2021 + */
  1.2022 +function getVisibleCookies(location) {
  1.2023 +  let results = [];
  1.2024 +  let currentPath = location.pathname;
  1.2025 +  if (!currentPath) currentPath = '/';
  1.2026 +  let isForCurrentPath = function(aPath) {
  1.2027 +    return currentPath.indexOf(aPath) != -1;
  1.2028 +  }
  1.2029 +
  1.2030 +  let cookieManager = Cc['@mozilla.org/cookiemanager;1'].
  1.2031 +                        getService(Ci.nsICookieManager);
  1.2032 +  let enumerator = cookieManager.enumerator;
  1.2033 +  while (enumerator.hasMoreElements()) {
  1.2034 +    let cookie = enumerator.getNext().QueryInterface(Ci['nsICookie']);
  1.2035 +
  1.2036 +    // Take the hostname and progressively shorten
  1.2037 +    let hostname = location.hostname;
  1.2038 +    do {
  1.2039 +      if ((cookie.host == '.' + hostname || cookie.host == hostname)
  1.2040 +          && isForCurrentPath(cookie.path)) {
  1.2041 +          results.push(cookie);
  1.2042 +          break;
  1.2043 +      }
  1.2044 +      hostname = hostname.replace(/^.*?\./, '');
  1.2045 +    } while (hostname.indexOf('.') != -1);
  1.2046 +  }
  1.2047 +
  1.2048 +  return results;
  1.2049 +}
  1.2050 +
  1.2051 +function getAppCacheStatus(msg) {
  1.2052 +  sendResponse({ value: curFrame.applicationCache.status },
  1.2053 +               msg.json.command_id);
  1.2054 +}
  1.2055 +
  1.2056 +// emulator callbacks
  1.2057 +let _emu_cb_id = 0;
  1.2058 +let _emu_cbs = {};
  1.2059 +
  1.2060 +function runEmulatorCmd(cmd, callback) {
  1.2061 +  if (callback) {
  1.2062 +    _emu_cbs[_emu_cb_id] = callback;
  1.2063 +  }
  1.2064 +  sendAsyncMessage("Marionette:runEmulatorCmd", {emulator_cmd: cmd, id: _emu_cb_id});
  1.2065 +  _emu_cb_id += 1;
  1.2066 +}
  1.2067 +
  1.2068 +function runEmulatorShell(args, callback) {
  1.2069 +  if (callback) {
  1.2070 +    _emu_cbs[_emu_cb_id] = callback;
  1.2071 +  }
  1.2072 +  sendAsyncMessage("Marionette:runEmulatorShell", {emulator_shell: args, id: _emu_cb_id});
  1.2073 +  _emu_cb_id += 1;
  1.2074 +}
  1.2075 +
  1.2076 +function emulatorCmdResult(msg) {
  1.2077 +  let message = msg.json;
  1.2078 +  if (!sandbox) {
  1.2079 +    return;
  1.2080 +  }
  1.2081 +  let cb = _emu_cbs[message.id];
  1.2082 +  delete _emu_cbs[message.id];
  1.2083 +  if (!cb) {
  1.2084 +    return;
  1.2085 +  }
  1.2086 +  try {
  1.2087 +    cb(message.result);
  1.2088 +  }
  1.2089 +  catch(e) {
  1.2090 +    sendError(e.message, e.code, e.stack, -1);
  1.2091 +    return;
  1.2092 +  }
  1.2093 +}
  1.2094 +
  1.2095 +function importScript(msg) {
  1.2096 +  let command_id = msg.json.command_id;
  1.2097 +  let file;
  1.2098 +  if (importedScripts.exists()) {
  1.2099 +    file = FileUtils.openFileOutputStream(importedScripts,
  1.2100 +        FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY);
  1.2101 +  }
  1.2102 +  else {
  1.2103 +    //Note: The permission bits here don't actually get set (bug 804563)
  1.2104 +    importedScripts.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE,
  1.2105 +                                 parseInt("0666", 8));
  1.2106 +    file = FileUtils.openFileOutputStream(importedScripts,
  1.2107 +                                          FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE);
  1.2108 +    importedScripts.permissions = parseInt("0666", 8); //actually set permissions
  1.2109 +  }
  1.2110 +  file.write(msg.json.script, msg.json.script.length);
  1.2111 +  file.close();
  1.2112 +  sendOk(command_id);
  1.2113 +}
  1.2114 +
  1.2115 +/**
  1.2116 + * Takes a screen capture of the given web element if <code>id</code>
  1.2117 + * property exists in the message's JSON object, or if null captures
  1.2118 + * the bounding box of the current frame.
  1.2119 + *
  1.2120 + * If given an array of web element references in
  1.2121 + * <code>msg.json.highlights</code>, a red box will be painted around
  1.2122 + * them to highlight their position.
  1.2123 + */
  1.2124 +function takeScreenshot(msg) {
  1.2125 +  let node = null;
  1.2126 +  if (msg.json.id) {
  1.2127 +    try {
  1.2128 +      node = elementManager.getKnownElement(msg.json.id, curFrame)
  1.2129 +    }
  1.2130 +    catch (e) {
  1.2131 +      sendResponse(e.message, e.code, e.stack, msg.json.command_id);
  1.2132 +      return;
  1.2133 +    }
  1.2134 +  }
  1.2135 +  else {
  1.2136 +    node = curFrame;
  1.2137 +  }
  1.2138 +  let highlights = msg.json.highlights;
  1.2139 +
  1.2140 +  var document = curFrame.document;
  1.2141 +  var rect, win, width, height, left, top;
  1.2142 +  // node can be either a window or an arbitrary DOM node
  1.2143 +  if (node == curFrame) {
  1.2144 +    // node is a window
  1.2145 +    win = node;
  1.2146 +    width = document.body.scrollWidth;
  1.2147 +    height = document.body.scrollHeight;
  1.2148 +    top = 0;
  1.2149 +    left = 0;
  1.2150 +  }
  1.2151 +  else {
  1.2152 +    // node is an arbitrary DOM node
  1.2153 +    win = node.ownerDocument.defaultView;
  1.2154 +    rect = node.getBoundingClientRect();
  1.2155 +    width = rect.width;
  1.2156 +    height = rect.height;
  1.2157 +    top = rect.top;
  1.2158 +    left = rect.left;
  1.2159 +  }
  1.2160 +
  1.2161 +  var canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
  1.2162 +                                        "canvas");
  1.2163 +  canvas.width = width;
  1.2164 +  canvas.height = height;
  1.2165 +  var ctx = canvas.getContext("2d");
  1.2166 +  // Draws the DOM contents of the window to the canvas
  1.2167 +  ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
  1.2168 +
  1.2169 +  // This section is for drawing a red rectangle around each element
  1.2170 +  // passed in via the highlights array
  1.2171 +  if (highlights) {
  1.2172 +    ctx.lineWidth = "2";
  1.2173 +    ctx.strokeStyle = "red";
  1.2174 +    ctx.save();
  1.2175 +
  1.2176 +    for (var i = 0; i < highlights.length; ++i) {
  1.2177 +      var elem = elementManager.getKnownElement(highlights[i], curFrame);
  1.2178 +      rect = elem.getBoundingClientRect();
  1.2179 +
  1.2180 +      var offsetY = -top;
  1.2181 +      var offsetX = -left;
  1.2182 +
  1.2183 +      // Draw the rectangle
  1.2184 +      ctx.strokeRect(rect.left + offsetX,
  1.2185 +                     rect.top + offsetY,
  1.2186 +                     rect.width,
  1.2187 +                     rect.height);
  1.2188 +    }
  1.2189 +  }
  1.2190 +
  1.2191 +  // Return the Base64 encoded string back to the client so that it
  1.2192 +  // can save the file to disk if it is required
  1.2193 +  var dataUrl = canvas.toDataURL("image/png", "");
  1.2194 +  var data = dataUrl.substring(dataUrl.indexOf(",") + 1);
  1.2195 +  sendResponse({value: data}, msg.json.command_id);
  1.2196 +}
  1.2197 +
  1.2198 +// Call register self when we get loaded
  1.2199 +registerSelf();

mercurial