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