testing/marionette/marionette-listener.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial