michael@0: /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: let uuidGen = Cc["@mozilla.org/uuid-generator;1"] michael@0: .getService(Ci.nsIUUIDGenerator); michael@0: michael@0: let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] michael@0: .getService(Ci.mozIJSSubScriptLoader); michael@0: michael@0: loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js"); michael@0: loader.loadSubScript("chrome://marionette/content/marionette-common.js"); michael@0: Cu.import("chrome://marionette/content/marionette-elements.js"); michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: let utils = {}; michael@0: utils.window = content; michael@0: // Load Event/ChromeUtils for use with JS scripts: michael@0: loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils); michael@0: loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils); michael@0: loader.loadSubScript("chrome://marionette/content/atoms.js", utils); michael@0: loader.loadSubScript("chrome://marionette/content/marionette-sendkeys.js", utils); michael@0: michael@0: loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js"); michael@0: loader.loadSubScript("chrome://specialpowers/content/specialpowers.js"); michael@0: michael@0: let marionetteLogObj = new MarionetteLogObj(); michael@0: michael@0: let isB2G = false; michael@0: michael@0: let marionetteTestName; michael@0: let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: let listenerId = null; //unique ID of this listener michael@0: let curFrame = content; michael@0: let previousFrame = null; michael@0: let elementManager = new ElementManager([]); michael@0: let importedScripts = null; michael@0: let inputSource = null; michael@0: michael@0: // The sandbox we execute test scripts in. Gets lazily created in michael@0: // createExecuteContentSandbox(). michael@0: let sandbox; michael@0: michael@0: // the unload handler michael@0: let onunload; michael@0: michael@0: // Flag to indicate whether an async script is currently running or not. michael@0: let asyncTestRunning = false; michael@0: let asyncTestCommandId; michael@0: let asyncTestTimeoutId; michael@0: michael@0: let inactivityTimeoutId = null; michael@0: let heartbeatCallback = function () {}; // Called by the simpletest methods. michael@0: michael@0: let originalOnError; michael@0: //timer for doc changes michael@0: let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: //timer for readystate michael@0: let readyStateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: // Send move events about this often michael@0: let EVENT_INTERVAL = 30; // milliseconds michael@0: // For assigning unique ids to all touches michael@0: let nextTouchId = 1000; michael@0: //Keep track of active Touches michael@0: let touchIds = {}; michael@0: // last touch for each fingerId michael@0: let multiLast = {}; michael@0: let lastCoordinates = null; michael@0: let isTap = false; michael@0: let scrolling = false; michael@0: // whether to send mouse event michael@0: let mouseEventsOnly = false; michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: let logger = Log.repository.getLogger("Marionette"); michael@0: logger.info("loaded marionette-listener.js"); michael@0: let modalHandler = function() { michael@0: // This gets called on the system app only since it receives the mozbrowserprompt event michael@0: sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true }); michael@0: let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value; michael@0: if (isLocal) { michael@0: previousFrame = curFrame; michael@0: } michael@0: curFrame = content; michael@0: sandbox = null; michael@0: }; michael@0: michael@0: /** michael@0: * Called when listener is first started up. michael@0: * The listener sends its unique window ID and its current URI to the actor. michael@0: * If the actor returns an ID, we start the listeners. Otherwise, nothing happens. michael@0: */ michael@0: function registerSelf() { michael@0: let msg = {value: winUtil.outerWindowID, href: content.location.href}; michael@0: // register will have the ID and a boolean describing if this is the main process or not michael@0: let register = sendSyncMessage("Marionette:register", msg); michael@0: michael@0: if (register[0]) { michael@0: listenerId = register[0][0].id; michael@0: // check if we're the main process michael@0: if (register[0][1] == true) { michael@0: addMessageListener("MarionetteMainListener:emitTouchEvent", emitTouchEventForIFrame); michael@0: } michael@0: importedScripts = FileUtils.getDir('TmpD', [], false); michael@0: importedScripts.append('marionetteContentScripts'); michael@0: startListeners(); michael@0: } michael@0: } michael@0: michael@0: function emitTouchEventForIFrame(message) { michael@0: let message = message.json; michael@0: let frames = curFrame.document.getElementsByTagName("iframe"); michael@0: let iframe = frames[message.index]; michael@0: let identifier = touchId = nextTouchId++; michael@0: let tabParent = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.tabParent; michael@0: tabParent.injectTouchEvent(message.type, [identifier], michael@0: [message.clientX], [message.clientY], michael@0: [message.radiusX], [message.radiusY], michael@0: [message.rotationAngle], [message.force], michael@0: 1, 0); michael@0: } michael@0: michael@0: /** michael@0: * Add a message listener that's tied to our listenerId. michael@0: */ michael@0: function addMessageListenerId(messageName, handler) { michael@0: addMessageListener(messageName + listenerId, handler); michael@0: } michael@0: michael@0: /** michael@0: * Remove a message listener that's tied to our listenerId. michael@0: */ michael@0: function removeMessageListenerId(messageName, handler) { michael@0: removeMessageListener(messageName + listenerId, handler); michael@0: } michael@0: michael@0: /** michael@0: * Start all message listeners michael@0: */ michael@0: function startListeners() { michael@0: addMessageListenerId("Marionette:newSession", newSession); michael@0: addMessageListenerId("Marionette:executeScript", executeScript); michael@0: addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript); michael@0: addMessageListenerId("Marionette:executeJSScript", executeJSScript); michael@0: addMessageListenerId("Marionette:singleTap", singleTap); michael@0: addMessageListenerId("Marionette:actionChain", actionChain); michael@0: addMessageListenerId("Marionette:multiAction", multiAction); michael@0: addMessageListenerId("Marionette:get", get); michael@0: addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl); michael@0: addMessageListenerId("Marionette:getTitle", getTitle); michael@0: addMessageListenerId("Marionette:getPageSource", getPageSource); michael@0: addMessageListenerId("Marionette:goBack", goBack); michael@0: addMessageListenerId("Marionette:goForward", goForward); michael@0: addMessageListenerId("Marionette:refresh", refresh); michael@0: addMessageListenerId("Marionette:findElementContent", findElementContent); michael@0: addMessageListenerId("Marionette:findElementsContent", findElementsContent); michael@0: addMessageListenerId("Marionette:getActiveElement", getActiveElement); michael@0: addMessageListenerId("Marionette:clickElement", clickElement); michael@0: addMessageListenerId("Marionette:getElementAttribute", getElementAttribute); michael@0: addMessageListenerId("Marionette:getElementText", getElementText); michael@0: addMessageListenerId("Marionette:getElementTagName", getElementTagName); michael@0: addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed); michael@0: addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty); michael@0: addMessageListenerId("Marionette:submitElement", submitElement); michael@0: addMessageListenerId("Marionette:getElementSize", getElementSize); michael@0: addMessageListenerId("Marionette:isElementEnabled", isElementEnabled); michael@0: addMessageListenerId("Marionette:isElementSelected", isElementSelected); michael@0: addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); michael@0: addMessageListenerId("Marionette:getElementLocation", getElementLocation); michael@0: addMessageListenerId("Marionette:clearElement", clearElement); michael@0: addMessageListenerId("Marionette:switchToFrame", switchToFrame); michael@0: addMessageListenerId("Marionette:deleteSession", deleteSession); michael@0: addMessageListenerId("Marionette:sleepSession", sleepSession); michael@0: addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult); michael@0: addMessageListenerId("Marionette:importScript", importScript); michael@0: addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus); michael@0: addMessageListenerId("Marionette:setTestName", setTestName); michael@0: addMessageListenerId("Marionette:takeScreenshot", takeScreenshot); michael@0: addMessageListenerId("Marionette:addCookie", addCookie); michael@0: addMessageListenerId("Marionette:getCookies", getCookies); michael@0: addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies); michael@0: addMessageListenerId("Marionette:deleteCookie", deleteCookie); michael@0: } michael@0: michael@0: /** michael@0: * Used during newSession and restart, called to set up the modal dialog listener in b2g michael@0: */ michael@0: function waitForReady() { michael@0: if (content.document.readyState == 'complete') { michael@0: readyStateTimer.cancel(); michael@0: content.addEventListener("mozbrowsershowmodalprompt", modalHandler, false); michael@0: content.addEventListener("unload", waitForReady, false); michael@0: } michael@0: else { michael@0: readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Called when we start a new session. It registers the michael@0: * current environment, and resets all values michael@0: */ michael@0: function newSession(msg) { michael@0: isB2G = msg.json.B2G; michael@0: resetValues(); michael@0: if (isB2G) { michael@0: readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: // We have to set correct mouse event source to MOZ_SOURCE_TOUCH michael@0: // to offer a way for event listeners to differentiate michael@0: // events being the result of a physical mouse action. michael@0: // This is especially important for the touch event shim, michael@0: // in order to prevent creating touch event for these fake mouse events. michael@0: inputSource = Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Puts the current session to sleep, so all listeners are removed except michael@0: * for the 'restart' listener. This is used to keep the content listener michael@0: * alive for reuse in B2G instead of reloading it each time. michael@0: */ michael@0: function sleepSession(msg) { michael@0: deleteSession(); michael@0: addMessageListener("Marionette:restart", restart); michael@0: } michael@0: michael@0: /** michael@0: * Restarts all our listeners after this listener was put to sleep michael@0: */ michael@0: function restart(msg) { michael@0: removeMessageListener("Marionette:restart", restart); michael@0: if (isB2G) { michael@0: readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: registerSelf(); michael@0: } michael@0: michael@0: /** michael@0: * Removes all listeners michael@0: */ michael@0: function deleteSession(msg) { michael@0: removeMessageListenerId("Marionette:newSession", newSession); michael@0: removeMessageListenerId("Marionette:executeScript", executeScript); michael@0: removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript); michael@0: removeMessageListenerId("Marionette:executeJSScript", executeJSScript); michael@0: removeMessageListenerId("Marionette:singleTap", singleTap); michael@0: removeMessageListenerId("Marionette:actionChain", actionChain); michael@0: removeMessageListenerId("Marionette:multiAction", multiAction); michael@0: removeMessageListenerId("Marionette:get", get); michael@0: removeMessageListenerId("Marionette:getTitle", getTitle); michael@0: removeMessageListenerId("Marionette:getPageSource", getPageSource); michael@0: removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl); michael@0: removeMessageListenerId("Marionette:goBack", goBack); michael@0: removeMessageListenerId("Marionette:goForward", goForward); michael@0: removeMessageListenerId("Marionette:refresh", refresh); michael@0: removeMessageListenerId("Marionette:findElementContent", findElementContent); michael@0: removeMessageListenerId("Marionette:findElementsContent", findElementsContent); michael@0: removeMessageListenerId("Marionette:getActiveElement", getActiveElement); michael@0: removeMessageListenerId("Marionette:clickElement", clickElement); michael@0: removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute); michael@0: removeMessageListenerId("Marionette:getElementTagName", getElementTagName); michael@0: removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed); michael@0: removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty); michael@0: removeMessageListenerId("Marionette:submitElement", submitElement); michael@0: removeMessageListenerId("Marionette:getElementSize", getElementSize); michael@0: removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled); michael@0: removeMessageListenerId("Marionette:isElementSelected", isElementSelected); michael@0: removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); michael@0: removeMessageListenerId("Marionette:getElementLocation", getElementLocation); michael@0: removeMessageListenerId("Marionette:clearElement", clearElement); michael@0: removeMessageListenerId("Marionette:switchToFrame", switchToFrame); michael@0: removeMessageListenerId("Marionette:deleteSession", deleteSession); michael@0: removeMessageListenerId("Marionette:sleepSession", sleepSession); michael@0: removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult); michael@0: removeMessageListenerId("Marionette:importScript", importScript); michael@0: removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus); michael@0: removeMessageListenerId("Marionette:setTestName", setTestName); michael@0: removeMessageListenerId("Marionette:takeScreenshot", takeScreenshot); michael@0: removeMessageListenerId("Marionette:addCookie", addCookie); michael@0: removeMessageListenerId("Marionette:getCookies", getCookies); michael@0: removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies); michael@0: removeMessageListenerId("Marionette:deleteCookie", deleteCookie); michael@0: if (isB2G) { michael@0: content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false); michael@0: } michael@0: elementManager.reset(); michael@0: // reset frame to the top-most frame michael@0: curFrame = content; michael@0: curFrame.focus(); michael@0: touchIds = {}; michael@0: } michael@0: michael@0: /* michael@0: * Helper methods michael@0: */ michael@0: michael@0: /** michael@0: * Generic method to send a message to the server michael@0: */ michael@0: function sendToServer(msg, value, command_id) { michael@0: if (command_id) { michael@0: value.command_id = command_id; michael@0: } michael@0: sendAsyncMessage(msg, value); michael@0: } michael@0: michael@0: /** michael@0: * Send response back to server michael@0: */ michael@0: function sendResponse(value, command_id) { michael@0: sendToServer("Marionette:done", value, command_id); michael@0: } michael@0: michael@0: /** michael@0: * Send ack back to server michael@0: */ michael@0: function sendOk(command_id) { michael@0: sendToServer("Marionette:ok", {}, command_id); michael@0: } michael@0: michael@0: /** michael@0: * Send log message to server michael@0: */ michael@0: function sendLog(msg) { michael@0: sendToServer("Marionette:log", { message: msg }); michael@0: } michael@0: michael@0: /** michael@0: * Send error message to server michael@0: */ michael@0: function sendError(message, status, trace, command_id) { michael@0: let error_msg = { message: message, status: status, stacktrace: trace }; michael@0: sendToServer("Marionette:error", error_msg, command_id); michael@0: } michael@0: michael@0: /** michael@0: * Clear test values after completion of test michael@0: */ michael@0: function resetValues() { michael@0: sandbox = null; michael@0: curFrame = content; michael@0: mouseEventsOnly = false; michael@0: } michael@0: michael@0: /** michael@0: * Dump a logline to stdout. Prepends logline with a timestamp. michael@0: */ michael@0: function dumpLog(logline) { michael@0: dump(Date.now() + " Marionette: " + logline); michael@0: } michael@0: michael@0: /** michael@0: * Check if our context was interrupted michael@0: */ michael@0: function wasInterrupted() { michael@0: if (previousFrame) { michael@0: let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2)); michael@0: if (element.id.indexOf("modal-dialog") == -1) { michael@0: return true; michael@0: } michael@0: else { michael@0: return false; michael@0: } michael@0: } michael@0: return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value; michael@0: } michael@0: michael@0: /* michael@0: * Marionette Methods michael@0: */ michael@0: michael@0: /** michael@0: * Returns a content sandbox that can be used by the execute_foo functions. michael@0: */ michael@0: function createExecuteContentSandbox(aWindow, timeout) { michael@0: let sandbox = new Cu.Sandbox(aWindow, {sandboxPrototype: aWindow}); michael@0: sandbox.global = sandbox; michael@0: sandbox.window = aWindow; michael@0: sandbox.document = sandbox.window.document; michael@0: sandbox.navigator = sandbox.window.navigator; michael@0: sandbox.testUtils = utils; michael@0: sandbox.asyncTestCommandId = asyncTestCommandId; michael@0: michael@0: let marionette = new Marionette(this, aWindow, "content", michael@0: marionetteLogObj, timeout, michael@0: heartbeatCallback, michael@0: marionetteTestName); michael@0: sandbox.marionette = marionette; michael@0: marionette.exports.forEach(function(fn) { michael@0: try { michael@0: sandbox[fn] = marionette[fn].bind(marionette); michael@0: } michael@0: catch(e) { michael@0: sandbox[fn] = marionette[fn]; michael@0: } michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(sandbox, 'SpecialPowers', function() { michael@0: return new SpecialPowers(aWindow); michael@0: }); michael@0: michael@0: sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) { michael@0: if (commandId == asyncTestCommandId) { michael@0: curFrame.removeEventListener("unload", onunload, false); michael@0: curFrame.clearTimeout(asyncTestTimeoutId); michael@0: michael@0: if (inactivityTimeoutId != null) { michael@0: curFrame.clearTimeout(inactivityTimeoutId); michael@0: } michael@0: michael@0: michael@0: sendSyncMessage("Marionette:shareData", michael@0: {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); michael@0: marionetteLogObj.clearLogs(); michael@0: michael@0: if (status == 0){ michael@0: if (Object.keys(_emu_cbs).length) { michael@0: _emu_cbs = {}; michael@0: sendError("Emulator callback still pending when finish() called", michael@0: 500, null, commandId); michael@0: } michael@0: else { michael@0: sendResponse({value: elementManager.wrapValue(value), status: status}, michael@0: commandId); michael@0: } michael@0: } michael@0: else { michael@0: sendError(value, status, stack, commandId); michael@0: } michael@0: michael@0: asyncTestRunning = false; michael@0: asyncTestTimeoutId = undefined; michael@0: asyncTestCommandId = undefined; michael@0: inactivityTimeoutId = null; michael@0: } michael@0: }; michael@0: sandbox.finish = function sandbox_finish() { michael@0: if (asyncTestRunning) { michael@0: sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId); michael@0: } else { michael@0: return marionette.generate_results(); michael@0: } michael@0: }; michael@0: sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) { michael@0: return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId); michael@0: }; michael@0: michael@0: return sandbox; michael@0: } michael@0: michael@0: /** michael@0: * Execute the given script either as a function body (executeScript) michael@0: * or directly (for 'mochitest' like JS Marionette tests) michael@0: */ michael@0: function executeScript(msg, directInject) { michael@0: // Set up inactivity timeout. michael@0: if (msg.json.inactivityTimeout) { michael@0: let setTimer = function() { michael@0: inactivityTimeoutId = curFrame.setTimeout(function() { michael@0: sendError('timed out due to inactivity', 28, null, asyncTestCommandId); michael@0: }, msg.json.inactivityTimeout); michael@0: }; michael@0: michael@0: setTimer(); michael@0: heartbeatCallback = function resetInactivityTimeout() { michael@0: curFrame.clearTimeout(inactivityTimeoutId); michael@0: setTimer(); michael@0: }; michael@0: } michael@0: michael@0: asyncTestCommandId = msg.json.command_id; michael@0: let script = msg.json.script; michael@0: michael@0: if (msg.json.newSandbox || !sandbox) { michael@0: sandbox = createExecuteContentSandbox(curFrame, michael@0: msg.json.timeout); michael@0: if (!sandbox) { michael@0: sendError("Could not create sandbox!", 500, null, asyncTestCommandId); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: sandbox.asyncTestCommandId = asyncTestCommandId; michael@0: } michael@0: michael@0: try { michael@0: if (directInject) { michael@0: if (importedScripts.exists()) { michael@0: let stream = Components.classes["@mozilla.org/network/file-input-stream;1"]. michael@0: createInstance(Components.interfaces.nsIFileInputStream); michael@0: stream.init(importedScripts, -1, 0, 0); michael@0: let data = NetUtil.readInputStreamToString(stream, stream.available()); michael@0: script = data + script; michael@0: } michael@0: let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file" ,0); michael@0: sendSyncMessage("Marionette:shareData", michael@0: {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); michael@0: marionetteLogObj.clearLogs(); michael@0: michael@0: if (res == undefined || res.passed == undefined) { michael@0: sendError("Marionette.finish() not called", 17, null, asyncTestCommandId); michael@0: } michael@0: else { michael@0: sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId); michael@0: } michael@0: } michael@0: else { michael@0: try { michael@0: sandbox.__marionetteParams = elementManager.convertWrappedArguments( michael@0: msg.json.args, curFrame); michael@0: } michael@0: catch(e) { michael@0: sendError(e.message, e.code, e.stack, asyncTestCommandId); michael@0: return; michael@0: } michael@0: michael@0: script = "let __marionetteFunc = function(){" + script + "};" + michael@0: "__marionetteFunc.apply(null, __marionetteParams);"; michael@0: if (importedScripts.exists()) { michael@0: let stream = Components.classes["@mozilla.org/network/file-input-stream;1"]. michael@0: createInstance(Components.interfaces.nsIFileInputStream); michael@0: stream.init(importedScripts, -1, 0, 0); michael@0: let data = NetUtil.readInputStreamToString(stream, stream.available()); michael@0: script = data + script; michael@0: } michael@0: let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0); michael@0: sendSyncMessage("Marionette:shareData", michael@0: {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); michael@0: marionetteLogObj.clearLogs(); michael@0: sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId); michael@0: } michael@0: } michael@0: catch (e) { michael@0: // 17 = JavascriptException michael@0: let error = createStackMessage(e, michael@0: "execute_script", michael@0: msg.json.filename, michael@0: msg.json.line, michael@0: script); michael@0: sendError(error[0], 17, error[1], asyncTestCommandId); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Sets the test name, used in logging messages. michael@0: */ michael@0: function setTestName(msg) { michael@0: marionetteTestName = msg.json.value; michael@0: sendOk(msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Execute async script michael@0: */ michael@0: function executeAsyncScript(msg) { michael@0: executeWithCallback(msg); michael@0: } michael@0: michael@0: /** michael@0: * Execute pure JS test. Handles both async and sync cases. michael@0: */ michael@0: function executeJSScript(msg) { michael@0: if (msg.json.async) { michael@0: executeWithCallback(msg, msg.json.async); michael@0: } michael@0: else { michael@0: executeScript(msg, true); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This function is used by executeAsync and executeJSScript to execute a script michael@0: * in a sandbox. michael@0: * michael@0: * For executeJSScript, it will return a message only when the finish() method is called. michael@0: * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] michael@0: * method is called, or if it times out. michael@0: */ michael@0: function executeWithCallback(msg, useFinish) { michael@0: // Set up inactivity timeout. michael@0: if (msg.json.inactivityTimeout) { michael@0: let setTimer = function() { michael@0: inactivityTimeoutId = curFrame.setTimeout(function() { michael@0: sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId); michael@0: }, msg.json.inactivityTimeout); michael@0: }; michael@0: michael@0: setTimer(); michael@0: heartbeatCallback = function resetInactivityTimeout() { michael@0: curFrame.clearTimeout(inactivityTimeoutId); michael@0: setTimer(); michael@0: }; michael@0: } michael@0: michael@0: let script = msg.json.script; michael@0: asyncTestCommandId = msg.json.command_id; michael@0: michael@0: onunload = function() { michael@0: sendError("unload was called", 17, null, asyncTestCommandId); michael@0: }; michael@0: curFrame.addEventListener("unload", onunload, false); michael@0: michael@0: if (msg.json.newSandbox || !sandbox) { michael@0: sandbox = createExecuteContentSandbox(curFrame, michael@0: msg.json.timeout); michael@0: if (!sandbox) { michael@0: sendError("Could not create sandbox!", 17, null, asyncTestCommandId); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: sandbox.asyncTestCommandId = asyncTestCommandId; michael@0: } michael@0: sandbox.tag = script; michael@0: michael@0: // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout), michael@0: // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async. michael@0: // However Selenium code returns 28, see michael@0: // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js. michael@0: // We'll stay compatible with the Selenium code. michael@0: asyncTestTimeoutId = curFrame.setTimeout(function() { michael@0: sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId); michael@0: }, msg.json.timeout); michael@0: michael@0: originalOnError = curFrame.onerror; michael@0: curFrame.onerror = function errHandler(errMsg, url, line) { michael@0: sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId); michael@0: curFrame.onerror = originalOnError; michael@0: }; michael@0: michael@0: let scriptSrc; michael@0: if (useFinish) { michael@0: if (msg.json.timeout == null || msg.json.timeout == 0) { michael@0: sendError("Please set a timeout", 21, null, asyncTestCommandId); michael@0: } michael@0: scriptSrc = script; michael@0: } michael@0: else { michael@0: try { michael@0: sandbox.__marionetteParams = elementManager.convertWrappedArguments( michael@0: msg.json.args, curFrame); michael@0: } michael@0: catch(e) { michael@0: sendError(e.message, e.code, e.stack, asyncTestCommandId); michael@0: return; michael@0: } michael@0: michael@0: scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" + michael@0: "let __marionetteFunc = function() { " + script + "};" + michael@0: "__marionetteFunc.apply(null, __marionetteParams); "; michael@0: } michael@0: michael@0: try { michael@0: asyncTestRunning = true; michael@0: if (importedScripts.exists()) { michael@0: let stream = Cc["@mozilla.org/network/file-input-stream;1"]. michael@0: createInstance(Ci.nsIFileInputStream); michael@0: stream.init(importedScripts, -1, 0, 0); michael@0: let data = NetUtil.readInputStreamToString(stream, stream.available()); michael@0: scriptSrc = data + scriptSrc; michael@0: } michael@0: Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0); michael@0: } catch (e) { michael@0: // 17 = JavascriptException michael@0: let error = createStackMessage(e, michael@0: "execute_async_script", michael@0: msg.json.filename, michael@0: msg.json.line, michael@0: scriptSrc); michael@0: sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This function creates a touch event given a touch type and a touch michael@0: */ michael@0: function emitTouchEvent(type, touch) { michael@0: if (!wasInterrupted()) { michael@0: 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: dumpLog(loggingInfo); michael@0: var docShell = curFrame.document.defaultView. michael@0: QueryInterface(Components.interfaces.nsIInterfaceRequestor). michael@0: getInterface(Components.interfaces.nsIWebNavigation). michael@0: QueryInterface(Components.interfaces.nsIDocShell); michael@0: if (docShell.asyncPanZoomEnabled && scrolling) { michael@0: // if we're in APZ and we're scrolling, we must use injectTouchEvent to dispatch our touchmove events michael@0: let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId"); michael@0: // only call emitTouchEventForIFrame if we're inside an iframe. michael@0: if (index != null) { michael@0: sendSyncMessage("Marionette:emitTouchEvent", {index: index, type: type, id: touch.identifier, michael@0: clientX: touch.clientX, clientY: touch.clientY, michael@0: radiusX: touch.radiusX, radiusY: touch.radiusY, michael@0: rotation: touch.rotationAngle, force: touch.force}); michael@0: return; michael@0: } michael@0: } michael@0: // we get here if we're not in asyncPacZoomEnabled land, or if we're the main process michael@0: /* michael@0: Disabled per bug 888303 michael@0: marionetteLogObj.log(loggingInfo, "TRACE"); michael@0: sendSyncMessage("Marionette:shareData", michael@0: {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); michael@0: marionetteLogObj.clearLogs(); michael@0: */ michael@0: let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils); michael@0: domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This function emit mouse event michael@0: * @param: doc is the current document michael@0: * type is the type of event to dispatch michael@0: * clickCount is the number of clicks, button notes the mouse button michael@0: * elClientX and elClientY are the coordinates of the mouse relative to the viewport michael@0: */ michael@0: function emitMouseEvent(doc, type, elClientX, elClientY, clickCount, button) { michael@0: if (!wasInterrupted()) { michael@0: let loggingInfo = "emitting Mouse event of type " + type + " at coordinates (" + elClientX + ", " + elClientY + ") relative to the viewport"; michael@0: dumpLog(loggingInfo); michael@0: /* michael@0: Disabled per bug 888303 michael@0: marionetteLogObj.log(loggingInfo, "TRACE"); michael@0: sendSyncMessage("Marionette:shareData", michael@0: {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); michael@0: marionetteLogObj.clearLogs(); michael@0: */ michael@0: let win = doc.defaultView; michael@0: let domUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils); michael@0: domUtils.sendMouseEvent(type, elClientX, elClientY, button || 0, clickCount || 1, 0, false, 0, inputSource); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Helper function that perform a mouse tap michael@0: */ michael@0: function mousetap(doc, x, y) { michael@0: emitMouseEvent(doc, 'mousemove', x, y); michael@0: emitMouseEvent(doc, 'mousedown', x, y); michael@0: emitMouseEvent(doc, 'mouseup', x, y); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * This function generates a pair of coordinates relative to the viewport given a michael@0: * target element and coordinates relative to that element's top-left corner. michael@0: * @param 'x', and 'y' are the relative to the target. michael@0: * If they are not specified, then the center of the target is used. michael@0: */ michael@0: function coordinates(target, x, y) { michael@0: let box = target.getBoundingClientRect(); michael@0: if (x == null) { michael@0: x = box.width / 2; michael@0: } michael@0: if (y == null) { michael@0: y = box.height / 2; michael@0: } michael@0: let coords = {}; michael@0: coords.x = box.left + x; michael@0: coords.y = box.top + y; michael@0: return coords; michael@0: } michael@0: michael@0: /** michael@0: * This function returns if the element is in viewport michael@0: */ michael@0: function elementInViewport(el) { michael@0: let rect = el.getBoundingClientRect(); michael@0: let viewPort = {top: curFrame.pageYOffset, michael@0: left: curFrame.pageXOffset, michael@0: bottom: (curFrame.pageYOffset + curFrame.innerHeight), michael@0: right:(curFrame.pageXOffset + curFrame.innerWidth)}; michael@0: return (viewPort.left <= rect.right + curFrame.pageXOffset && michael@0: rect.left + curFrame.pageXOffset <= viewPort.right && michael@0: viewPort.top <= rect.bottom + curFrame.pageYOffset && michael@0: rect.top + curFrame.pageYOffset <= viewPort.bottom); michael@0: } michael@0: michael@0: /** michael@0: * This function throws the visibility of the element error michael@0: */ michael@0: function checkVisible(el) { michael@0: //check if the element is visible michael@0: let visible = utils.isElementDisplayed(el); michael@0: if (!visible) { michael@0: return false; michael@0: } michael@0: if (el.tagName.toLowerCase() === 'body') { michael@0: return true; michael@0: } michael@0: if (!elementInViewport(el)) { michael@0: //check if scroll function exist. If so, call it. michael@0: if (el.scrollIntoView) { michael@0: el.scrollIntoView(false); michael@0: if (!elementInViewport(el)) { michael@0: return false; michael@0: } michael@0: } michael@0: else { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: //x and y are coordinates relative to the viewport michael@0: function generateEvents(type, x, y, touchId, target) { michael@0: lastCoordinates = [x, y]; michael@0: let doc = curFrame.document; michael@0: switch (type) { michael@0: case 'tap': michael@0: if (mouseEventsOnly) { michael@0: mousetap(target.ownerDocument, x, y); michael@0: } michael@0: else { michael@0: let touchId = nextTouchId++; michael@0: let touch = createATouch(target, x, y, touchId); michael@0: emitTouchEvent('touchstart', touch); michael@0: emitTouchEvent('touchend', touch); michael@0: mousetap(target.ownerDocument, x, y); michael@0: } michael@0: lastCoordinates = null; michael@0: break; michael@0: case 'press': michael@0: isTap = true; michael@0: if (mouseEventsOnly) { michael@0: emitMouseEvent(doc, 'mousemove', x, y); michael@0: emitMouseEvent(doc, 'mousedown', x, y); michael@0: } michael@0: else { michael@0: let touchId = nextTouchId++; michael@0: let touch = createATouch(target, x, y, touchId); michael@0: emitTouchEvent('touchstart', touch); michael@0: touchIds[touchId] = touch; michael@0: return touchId; michael@0: } michael@0: break; michael@0: case 'release': michael@0: if (mouseEventsOnly) { michael@0: emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]); michael@0: } michael@0: else { michael@0: let touch = touchIds[touchId]; michael@0: touch = createATouch(touch.target, lastCoordinates[0], lastCoordinates[1], touchId); michael@0: emitTouchEvent('touchend', touch); michael@0: if (isTap) { michael@0: mousetap(touch.target.ownerDocument, touch.clientX, touch.clientY); michael@0: } michael@0: delete touchIds[touchId]; michael@0: } michael@0: isTap = false; michael@0: lastCoordinates = null; michael@0: break; michael@0: case 'cancel': michael@0: isTap = false; michael@0: if (mouseEventsOnly) { michael@0: emitMouseEvent(doc, 'mouseup', lastCoordinates[0], lastCoordinates[1]); michael@0: } michael@0: else { michael@0: emitTouchEvent('touchcancel', touchIds[touchId]); michael@0: delete touchIds[touchId]; michael@0: } michael@0: lastCoordinates = null; michael@0: break; michael@0: case 'move': michael@0: isTap = false; michael@0: if (mouseEventsOnly) { michael@0: emitMouseEvent(doc, 'mousemove', x, y); michael@0: } michael@0: else { michael@0: touch = createATouch(touchIds[touchId].target, x, y, touchId); michael@0: touchIds[touchId] = touch; michael@0: emitTouchEvent('touchmove', touch); michael@0: } michael@0: break; michael@0: case 'contextmenu': michael@0: isTap = false; michael@0: let event = curFrame.document.createEvent('HTMLEvents'); michael@0: event.initEvent('contextmenu', true, true); michael@0: if (mouseEventsOnly) { michael@0: target = doc.elementFromPoint(lastCoordinates[0], lastCoordinates[1]); michael@0: } michael@0: else { michael@0: target = touchIds[touchId].target; michael@0: } michael@0: target.dispatchEvent(event); michael@0: break; michael@0: default: michael@0: throw {message:"Unknown event type: " + type, code: 500, stack:null}; michael@0: } michael@0: if (wasInterrupted()) { michael@0: if (previousFrame) { michael@0: //if previousFrame is set, then we're in a single process environment michael@0: curFrame = previousFrame; michael@0: previousFrame = null; michael@0: sandbox = null; michael@0: } michael@0: else { michael@0: //else we're in OOP environment, so we'll switch to the original OOP frame michael@0: sendSyncMessage("Marionette:switchToModalOrigin"); michael@0: } michael@0: sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true }); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Function that perform a single tap michael@0: */ michael@0: function singleTap(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: // after this block, the element will be scrolled into view michael@0: if (!checkVisible(el)) { michael@0: sendError("Element is not currently visible and may not be manipulated", 11, null, command_id); michael@0: return; michael@0: } michael@0: if (!curFrame.document.createTouch) { michael@0: mouseEventsOnly = true; michael@0: } michael@0: let c = coordinates(el, msg.json.corx, msg.json.cory); michael@0: generateEvents('tap', c.x, c.y, null, el); michael@0: sendOk(msg.json.command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, msg.json.command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Function to create a touch based on the element michael@0: * corx and cory are relative to the viewport, id is the touchId michael@0: */ michael@0: function createATouch(el, corx, cory, touchId) { michael@0: let doc = el.ownerDocument; michael@0: let win = doc.defaultView; michael@0: let clientX = corx; michael@0: let clientY = cory; michael@0: let pageX = clientX + win.pageXOffset, michael@0: pageY = clientY + win.pageYOffset; michael@0: let screenX = clientX + win.mozInnerScreenX, michael@0: screenY = clientY + win.mozInnerScreenY; michael@0: let atouch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY); michael@0: return atouch; michael@0: } michael@0: michael@0: /** michael@0: * Function to emit touch events for each finger. e.g. finger=[['press', id], ['wait', 5], ['release']] michael@0: * touchId represents the finger id, i keeps track of the current action of the chain michael@0: */ michael@0: function actions(chain, touchId, command_id, i) { michael@0: if (typeof i === "undefined") { michael@0: i = 0; michael@0: } michael@0: if (i == chain.length) { michael@0: sendResponse({value: touchId}, command_id); michael@0: return; michael@0: } michael@0: let pack = chain[i]; michael@0: let command = pack[0]; michael@0: let el; michael@0: let c; michael@0: i++; michael@0: if (command != 'press') { michael@0: //if mouseEventsOnly, then touchIds isn't used michael@0: if (!(touchId in touchIds) && !mouseEventsOnly) { michael@0: sendError("Element has not been pressed", 500, null, command_id); michael@0: return; michael@0: } michael@0: } michael@0: switch(command) { michael@0: case 'press': michael@0: if (lastCoordinates) { michael@0: generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId); michael@0: sendError("Invalid Command: press cannot follow an active touch event", 500, null, command_id); michael@0: return; michael@0: } michael@0: // look ahead to check if we're scrolling. Needed for APZ touch dispatching. michael@0: if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) { michael@0: scrolling = true; michael@0: } michael@0: el = elementManager.getKnownElement(pack[1], curFrame); michael@0: c = coordinates(el, pack[2], pack[3]); michael@0: touchId = generateEvents('press', c.x, c.y, null, el); michael@0: actions(chain, touchId, command_id, i); michael@0: break; michael@0: case 'release': michael@0: generateEvents('release', lastCoordinates[0], lastCoordinates[1], touchId); michael@0: actions(chain, null, command_id, i); michael@0: scrolling = false; michael@0: break; michael@0: case 'move': michael@0: el = elementManager.getKnownElement(pack[1], curFrame); michael@0: c = coordinates(el); michael@0: generateEvents('move', c.x, c.y, touchId); michael@0: actions(chain, touchId, command_id, i); michael@0: break; michael@0: case 'moveByOffset': michael@0: generateEvents('move', lastCoordinates[0] + pack[1], lastCoordinates[1] + pack[2], touchId); michael@0: actions(chain, touchId, command_id, i); michael@0: break; michael@0: case 'wait': michael@0: if (pack[1] != null ) { michael@0: let time = pack[1]*1000; michael@0: // standard waiting time to fire contextmenu michael@0: let standard = 750; michael@0: try { michael@0: standard = Services.prefs.getIntPref("ui.click_hold_context_menus.delay"); michael@0: } michael@0: catch (e){} michael@0: if (time >= standard && isTap) { michael@0: chain.splice(i, 0, ['longPress'], ['wait', (time-standard)/1000]); michael@0: time = standard; michael@0: } michael@0: checkTimer.initWithCallback(function(){actions(chain, touchId, command_id, i);}, time, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: else { michael@0: actions(chain, touchId, command_id, i); michael@0: } michael@0: break; michael@0: case 'cancel': michael@0: generateEvents('cancel', lastCoordinates[0], lastCoordinates[1], touchId); michael@0: actions(chain, touchId, command_id, i); michael@0: scrolling = false; michael@0: break; michael@0: case 'longPress': michael@0: generateEvents('contextmenu', lastCoordinates[0], lastCoordinates[1], touchId); michael@0: actions(chain, touchId, command_id, i); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Function to start action chain on one finger michael@0: */ michael@0: function actionChain(msg) { michael@0: let command_id = msg.json.command_id; michael@0: let args = msg.json.chain; michael@0: let touchId = msg.json.nextId; michael@0: try { michael@0: let commandArray = elementManager.convertWrappedArguments(args, curFrame); michael@0: // loop the action array [ ['press', id], ['move', id], ['release', id] ] michael@0: if (touchId == null) { michael@0: touchId = nextTouchId++; michael@0: } michael@0: if (!curFrame.document.createTouch) { michael@0: mouseEventsOnly = true; michael@0: } michael@0: actions(commandArray, touchId, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, msg.json.command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Function to emit touch events which allow multi touch on the screen michael@0: * @param type represents the type of event, touch represents the current touch,touches are all pending touches michael@0: */ michael@0: function emitMultiEvents(type, touch, touches) { michael@0: let target = touch.target; michael@0: let doc = target.ownerDocument; michael@0: let win = doc.defaultView; michael@0: // touches that are in the same document michael@0: let documentTouches = doc.createTouchList(touches.filter(function(t) { michael@0: return ((t.target.ownerDocument === doc) && (type != 'touchcancel')); michael@0: })); michael@0: // touches on the same target michael@0: let targetTouches = doc.createTouchList(touches.filter(function(t) { michael@0: return ((t.target === target) && ((type != 'touchcancel') || (type != 'touchend'))); michael@0: })); michael@0: // Create changed touches michael@0: let changedTouches = doc.createTouchList(touch); michael@0: // Create the event object michael@0: let event = doc.createEvent('TouchEvent'); michael@0: event.initTouchEvent(type, michael@0: true, michael@0: true, michael@0: win, michael@0: 0, michael@0: false, false, false, false, michael@0: documentTouches, michael@0: targetTouches, michael@0: changedTouches); michael@0: target.dispatchEvent(event); michael@0: } michael@0: michael@0: /** michael@0: * Function to dispatch one set of actions michael@0: * @param touches represents all pending touches, batchIndex represents the batch we are dispatching right now michael@0: */ michael@0: function setDispatch(batches, touches, command_id, batchIndex) { michael@0: if (typeof batchIndex === "undefined") { michael@0: batchIndex = 0; michael@0: } michael@0: // check if all the sets have been fired michael@0: if (batchIndex >= batches.length) { michael@0: multiLast = {}; michael@0: sendOk(command_id); michael@0: return; michael@0: } michael@0: // a set of actions need to be done michael@0: let batch = batches[batchIndex]; michael@0: // each action for some finger michael@0: let pack; michael@0: // the touch id for the finger (pack) michael@0: let touchId; michael@0: // command for the finger michael@0: let command; michael@0: // touch that will be created for the finger michael@0: let el; michael@0: let corx; michael@0: let cory; michael@0: let touch; michael@0: let lastTouch; michael@0: let touchIndex; michael@0: let waitTime = 0; michael@0: let maxTime = 0; michael@0: let c; michael@0: batchIndex++; michael@0: // loop through the batch michael@0: for (let i = 0; i < batch.length; i++) { michael@0: pack = batch[i]; michael@0: touchId = pack[0]; michael@0: command = pack[1]; michael@0: switch (command) { michael@0: case 'press': michael@0: el = elementManager.getKnownElement(pack[2], curFrame); michael@0: c = coordinates(el, pack[3], pack[4]); michael@0: touch = createATouch(el, c.x, c.y, touchId); michael@0: multiLast[touchId] = touch; michael@0: touches.push(touch); michael@0: emitMultiEvents('touchstart', touch, touches); michael@0: break; michael@0: case 'release': michael@0: touch = multiLast[touchId]; michael@0: // the index of the previous touch for the finger may change in the touches array michael@0: touchIndex = touches.indexOf(touch); michael@0: touches.splice(touchIndex, 1); michael@0: emitMultiEvents('touchend', touch, touches); michael@0: break; michael@0: case 'move': michael@0: el = elementManager.getKnownElement(pack[2], curFrame); michael@0: c = coordinates(el); michael@0: touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId); michael@0: touchIndex = touches.indexOf(lastTouch); michael@0: touches[touchIndex] = touch; michael@0: multiLast[touchId] = touch; michael@0: emitMultiEvents('touchmove', touch, touches); michael@0: break; michael@0: case 'moveByOffset': michael@0: el = multiLast[touchId].target; michael@0: lastTouch = multiLast[touchId]; michael@0: touchIndex = touches.indexOf(lastTouch); michael@0: let doc = el.ownerDocument; michael@0: let win = doc.defaultView; michael@0: // since x and y are relative to the last touch, therefore, it's relative to the position of the last touch michael@0: let clientX = lastTouch.clientX + pack[2], michael@0: clientY = lastTouch.clientY + pack[3]; michael@0: let pageX = clientX + win.pageXOffset, michael@0: pageY = clientY + win.pageYOffset; michael@0: let screenX = clientX + win.mozInnerScreenX, michael@0: screenY = clientY + win.mozInnerScreenY; michael@0: touch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY); michael@0: touches[touchIndex] = touch; michael@0: multiLast[touchId] = touch; michael@0: emitMultiEvents('touchmove', touch, touches); michael@0: break; michael@0: case 'wait': michael@0: if (pack[2] != undefined ) { michael@0: waitTime = pack[2]*1000; michael@0: if (waitTime > maxTime) { michael@0: maxTime = waitTime; michael@0: } michael@0: } michael@0: break; michael@0: }//end of switch block michael@0: }//end of for loop michael@0: if (maxTime != 0) { michael@0: checkTimer.initWithCallback(function(){setDispatch(batches, touches, command_id, batchIndex);}, maxTime, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: else { michael@0: setDispatch(batches, touches, command_id, batchIndex); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Function to start multi-action michael@0: */ michael@0: function multiAction(msg) { michael@0: let command_id = msg.json.command_id; michael@0: let args = msg.json.value; michael@0: // maxlen is the longest action chain for one finger michael@0: let maxlen = msg.json.maxlen; michael@0: try { michael@0: // unwrap the original nested array michael@0: let commandArray = elementManager.convertWrappedArguments(args, curFrame); michael@0: let concurrentEvent = []; michael@0: let temp; michael@0: for (let i = 0; i < maxlen; i++) { michael@0: let row = []; michael@0: for (let j = 0; j < commandArray.length; j++) { michael@0: if (commandArray[j][i] != undefined) { michael@0: // add finger id to the front of each action, i.e. [finger_id, action, element] michael@0: temp = commandArray[j][i]; michael@0: temp.unshift(j); michael@0: row.push(temp); michael@0: } michael@0: } michael@0: concurrentEvent.push(row); michael@0: } michael@0: // now concurrent event is made of sets where each set contain a list of actions that need to be fired. michael@0: // note: each action belongs to a different finger michael@0: // pendingTouches keeps track of current touches that's on the screen michael@0: let pendingTouches = []; michael@0: setDispatch(concurrentEvent, pendingTouches, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, msg.json.command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Navigate to the given URL. The operation will be performed on the michael@0: * current browser context, and handles the case where we navigate michael@0: * within an iframe. All other navigation is handled by the server michael@0: * (in chrome space). michael@0: */ michael@0: function get(msg) { michael@0: let command_id = msg.json.command_id; michael@0: michael@0: let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: let start = new Date().getTime(); michael@0: let end = null; michael@0: function checkLoad() { michael@0: checkTimer.cancel(); michael@0: end = new Date().getTime(); michael@0: let errorRegex = /about:.+(error)|(blocked)\?/; michael@0: let elapse = end - start; michael@0: if (msg.json.pageTimeout == null || elapse <= msg.json.pageTimeout) { michael@0: if (curFrame.document.readyState == "complete") { michael@0: removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); michael@0: sendOk(command_id); michael@0: } michael@0: else if (curFrame.document.readyState == "interactive" && michael@0: errorRegex.exec(curFrame.document.baseURI)) { michael@0: removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); michael@0: sendError("Error loading page", 13, null, command_id); michael@0: } michael@0: else { michael@0: checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: else { michael@0: removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); michael@0: sendError("Error loading page, timed out (checkLoad)", 21, null, michael@0: command_id); michael@0: } michael@0: } michael@0: // Prevent DOMContentLoaded events from frames from invoking this michael@0: // code, unless the event is coming from the frame associated with michael@0: // the current window (i.e. someone has used switch_to_frame). michael@0: let onDOMContentLoaded = function onDOMContentLoaded(event) { michael@0: if (!event.originalTarget.defaultView.frameElement || michael@0: event.originalTarget.defaultView.frameElement == curFrame.frameElement) { michael@0: checkLoad(); michael@0: } michael@0: }; michael@0: michael@0: function timerFunc() { michael@0: removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); michael@0: sendError("Error loading page, timed out (onDOMContentLoaded)", 21, michael@0: null, command_id); michael@0: } michael@0: if (msg.json.pageTimeout != null) { michael@0: checkTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: addEventListener("DOMContentLoaded", onDOMContentLoaded, false); michael@0: curFrame.location = msg.json.url; michael@0: } michael@0: michael@0: /** michael@0: * Get URL of the top level browsing context. michael@0: */ michael@0: function getCurrentUrl(msg) { michael@0: sendResponse({value: curFrame.location.href}, msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Get the current Title of the window michael@0: */ michael@0: function getTitle(msg) { michael@0: sendResponse({value: curFrame.top.document.title}, msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Get the current page source michael@0: */ michael@0: function getPageSource(msg) { michael@0: var XMLSerializer = curFrame.XMLSerializer; michael@0: var pageSource = new XMLSerializer().serializeToString(curFrame.document); michael@0: sendResponse({value: pageSource}, msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Go back in history michael@0: */ michael@0: function goBack(msg) { michael@0: curFrame.history.back(); michael@0: sendOk(msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Go forward in history michael@0: */ michael@0: function goForward(msg) { michael@0: curFrame.history.forward(); michael@0: sendOk(msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Refresh the page michael@0: */ michael@0: function refresh(msg) { michael@0: let command_id = msg.json.command_id; michael@0: curFrame.location.reload(true); michael@0: let listen = function() { michael@0: removeEventListener("DOMContentLoaded", arguments.callee, false); michael@0: sendOk(command_id); michael@0: }; michael@0: addEventListener("DOMContentLoaded", listen, false); michael@0: } michael@0: michael@0: /** michael@0: * Find an element in the document using requested search strategy michael@0: */ michael@0: function findElementContent(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); }; michael@0: let on_error = sendError; michael@0: elementManager.find(curFrame, msg.json, msg.json.searchTimeout, michael@0: on_success, on_error, false, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Find elements in the document using requested search strategy michael@0: */ michael@0: function findElementsContent(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); }; michael@0: let on_error = sendError; michael@0: elementManager.find(curFrame, msg.json, msg.json.searchTimeout, michael@0: on_success, on_error, true, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Find and return the active element on the page michael@0: */ michael@0: function getActiveElement(msg) { michael@0: let command_id = msg.json.command_id; michael@0: var element = curFrame.document.activeElement; michael@0: var id = elementManager.addToKnownElements(element); michael@0: sendResponse({value: id}, command_id); michael@0: } michael@0: michael@0: /** michael@0: * Send click event to element michael@0: */ michael@0: function clickElement(msg) { michael@0: let command_id = msg.json.command_id; michael@0: let el; michael@0: try { michael@0: el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: if (checkVisible(el)) { michael@0: if (utils.isElementEnabled(el)) { michael@0: utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView) michael@0: } michael@0: else { michael@0: sendError("Element is not Enabled", 12, null, command_id) michael@0: } michael@0: } michael@0: else { michael@0: sendError("Element is not visible", 11, null, command_id) michael@0: } michael@0: sendOk(command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Get a given attribute of an element michael@0: */ michael@0: function getElementAttribute(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: sendResponse({value: utils.getElementAttribute(el, msg.json.name)}, michael@0: command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Get the text of this element. This includes text from child elements. michael@0: */ michael@0: function getElementText(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: sendResponse({value: utils.getElementText(el)}, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Get the tag name of an element. michael@0: */ michael@0: function getElementTagName(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: sendResponse({value: el.tagName.toLowerCase()}, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Check if element is displayed michael@0: */ michael@0: function isElementDisplayed(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: sendResponse({value: utils.isElementDisplayed(el)}, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Return the property of the computed style of an element michael@0: * michael@0: * @param object aRequest michael@0: * 'element' member holds the reference id to michael@0: * the element that will be checked michael@0: * 'propertyName' is the CSS rule that is being requested michael@0: */ michael@0: function getElementValueOfCssProperty(msg){ michael@0: let command_id = msg.json.command_id; michael@0: let propertyName = msg.json.propertyName; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)}, michael@0: command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Submit a form on a content page by either using form or element in a form michael@0: * @param object msg michael@0: * 'json' JSON object containing 'id' member of the element michael@0: */ michael@0: function submitElement (msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: while (el.parentNode != null && el.tagName.toLowerCase() != 'form') { michael@0: el = el.parentNode; michael@0: } michael@0: if (el.tagName && el.tagName.toLowerCase() == 'form') { michael@0: el.submit(); michael@0: sendOk(command_id); michael@0: } michael@0: else { michael@0: sendError("Element is not a form element or in a form", 7, null, command_id); michael@0: } michael@0: michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Get the size of the element and return it michael@0: */ michael@0: function getElementSize(msg){ michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: let clientRect = el.getBoundingClientRect(); michael@0: sendResponse({value: {width: clientRect.width, height: clientRect.height}}, michael@0: command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Check if element is enabled michael@0: */ michael@0: function isElementEnabled(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: sendResponse({value: utils.isElementEnabled(el)}, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Check if element is selected michael@0: */ michael@0: function isElementSelected(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: sendResponse({value: utils.isElementSelected(el)}, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Send keys to element michael@0: */ michael@0: function sendKeysToElement(msg) { michael@0: let command_id = msg.json.command_id; michael@0: michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: if (checkVisible(el)) { michael@0: if (el.mozIsTextField && el.mozIsTextField(false)) { michael@0: var currentTextLength = el.value ? el.value.length : 0; michael@0: el.selectionStart = currentTextLength; michael@0: el.selectionEnd = currentTextLength; michael@0: } michael@0: el.focus(); michael@0: var value = msg.json.value.join(""); michael@0: let hasShift = null; michael@0: let hasCtrl = null; michael@0: let hasAlt = null; michael@0: let hasMeta = null; michael@0: for (var i = 0; i < value.length; i++) { michael@0: let upper = value.charAt(i).toUpperCase(); michael@0: var keyCode = null; michael@0: var c = value.charAt(i); michael@0: switch (c) { michael@0: case '\uE001': michael@0: keyCode = "VK_CANCEL"; michael@0: break; michael@0: case '\uE002': michael@0: keyCode = "VK_HELP"; michael@0: break; michael@0: case '\uE003': michael@0: keyCode = "VK_BACK_SPACE"; michael@0: break; michael@0: case '\uE004': michael@0: keyCode = "VK_TAB"; michael@0: break; michael@0: case '\uE005': michael@0: keyCode = "VK_CLEAR"; michael@0: break; michael@0: case '\uE006': michael@0: case '\uE007': michael@0: keyCode = "VK_RETURN"; michael@0: break; michael@0: case '\uE008': michael@0: keyCode = "VK_SHIFT"; michael@0: hasShift = !hasShift; michael@0: break; michael@0: case '\uE009': michael@0: keyCode = "VK_CONTROL"; michael@0: controlKey = !controlKey; michael@0: break; michael@0: case '\uE00A': michael@0: keyCode = "VK_ALT"; michael@0: altKey = !altKey; michael@0: break; michael@0: case '\uE03D': michael@0: keyCode = "VK_META"; michael@0: metaKey = !metaKey; michael@0: break; michael@0: case '\uE00B': michael@0: keyCode = "VK_PAUSE"; michael@0: break; michael@0: case '\uE00C': michael@0: keyCode = "VK_ESCAPE"; michael@0: break; michael@0: case '\uE00D': michael@0: keyCode = "VK_Space"; // printable michael@0: break; michael@0: case '\uE00E': michael@0: keyCode = "VK_PAGE_UP"; michael@0: break; michael@0: case '\uE00F': michael@0: keyCode = "VK_PAGE_DOWN"; michael@0: break; michael@0: case '\uE010': michael@0: keyCode = "VK_END"; michael@0: break; michael@0: case '\uE011': michael@0: keyCode = "VK_HOME"; michael@0: break; michael@0: case '\uE012': michael@0: keyCode = "VK_LEFT"; michael@0: break; michael@0: case '\uE013': michael@0: keyCode = "VK_UP"; michael@0: break; michael@0: case '\uE014': michael@0: keyCode = "VK_RIGHT"; michael@0: break; michael@0: case '\uE015': michael@0: keyCode = "VK_DOWN"; michael@0: break; michael@0: case '\uE016': michael@0: keyCode = "VK_INSERT"; michael@0: break; michael@0: case '\uE017': michael@0: keyCode = "VK_DELETE"; michael@0: break; michael@0: case '\uE018': michael@0: keyCode = "VK_SEMICOLON"; michael@0: break; michael@0: case '\uE019': michael@0: keyCode = "VK_EQUALS"; michael@0: break; michael@0: case '\uE01A': michael@0: keyCode = "VK_NUMPAD0"; michael@0: break; michael@0: case '\uE01B': michael@0: keyCode = "VK_NUMPAD1"; michael@0: break; michael@0: case '\uE01C': michael@0: keyCode = "VK_NUMPAD2"; michael@0: break; michael@0: case '\uE01D': michael@0: keyCode = "VK_NUMPAD3"; michael@0: break; michael@0: case '\uE01E': michael@0: keyCode = "VK_NUMPAD4"; michael@0: break; michael@0: case '\uE01F': michael@0: keyCode = "VK_NUMPAD5"; michael@0: break; michael@0: case '\uE020': michael@0: keyCode = "VK_NUMPAD6"; michael@0: break; michael@0: case '\uE021': michael@0: keyCode = "VK_NUMPAD7"; michael@0: break; michael@0: case '\uE022': michael@0: keyCode = "VK_NUMPAD8"; michael@0: break; michael@0: case '\uE023': michael@0: keyCode = "VK_NUMPAD9"; michael@0: break; michael@0: case '\uE024': michael@0: keyCode = "VK_MULTIPLY"; michael@0: break; michael@0: case '\uE025': michael@0: keyCode = "VK_ADD"; michael@0: break; michael@0: case '\uE026': michael@0: keyCode = "VK_SEPARATOR"; michael@0: break; michael@0: case '\uE027': michael@0: keyCode = "VK_SUBTRACT"; michael@0: break; michael@0: case '\uE028': michael@0: keyCode = "VK_DECIMAL"; michael@0: break; michael@0: case '\uE029': michael@0: keyCode = "VK_DIVIDE"; michael@0: break; michael@0: case '\uE031': michael@0: keyCode = "VK_F1"; michael@0: break; michael@0: case '\uE032': michael@0: keyCode = "VK_F2"; michael@0: break; michael@0: case '\uE033': michael@0: keyCode = "VK_F3"; michael@0: break; michael@0: case '\uE034': michael@0: keyCode = "VK_F4"; michael@0: break; michael@0: case '\uE035': michael@0: keyCode = "VK_F5"; michael@0: break; michael@0: case '\uE036': michael@0: keyCode = "VK_F6"; michael@0: break; michael@0: case '\uE037': michael@0: keyCode = "VK_F7"; michael@0: break; michael@0: case '\uE038': michael@0: keyCode = "VK_F8"; michael@0: break; michael@0: case '\uE039': michael@0: keyCode = "VK_F9"; michael@0: break; michael@0: case '\uE03A': michael@0: keyCode = "VK_F10"; michael@0: break; michael@0: case '\uE03B': michael@0: keyCode = "VK_F11"; michael@0: break; michael@0: case '\uE03C': michael@0: keyCode = "VK_F12"; michael@0: break; michael@0: } michael@0: hasShift = value.charAt(i) == upper; michael@0: utils.synthesizeKey(keyCode || value[i], michael@0: { shiftKey: hasShift, ctrlKey: hasCtrl, altKey: hasAlt, metaKey: hasMeta }, michael@0: curFrame); michael@0: }; michael@0: sendOk(command_id); michael@0: } michael@0: else { michael@0: sendError("Element is not visible", 11, null, command_id) michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Get the element's top left-hand corner point. michael@0: */ michael@0: function getElementLocation(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: let rect = el.getBoundingClientRect(); michael@0: michael@0: let location = {}; michael@0: location.x = rect.left; michael@0: location.y = rect.top; michael@0: michael@0: sendResponse({value: location}, command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Clear the text of an element michael@0: */ michael@0: function clearElement(msg) { michael@0: let command_id = msg.json.command_id; michael@0: try { michael@0: let el = elementManager.getKnownElement(msg.json.id, curFrame); michael@0: utils.clearElement(el); michael@0: sendOk(command_id); michael@0: } michael@0: catch (e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Switch to frame given either the server-assigned element id, michael@0: * its index in window.frames, or the iframe's name or id. michael@0: */ michael@0: function switchToFrame(msg) { michael@0: let command_id = msg.json.command_id; michael@0: function checkLoad() { michael@0: let errorRegex = /about:.+(error)|(blocked)\?/; michael@0: if (curFrame.document.readyState == "complete") { michael@0: sendOk(command_id); michael@0: return; michael@0: } michael@0: else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) { michael@0: sendError("Error loading page", 13, null, command_id); michael@0: return; michael@0: } michael@0: checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: let foundFrame = null; michael@0: let frames = []; //curFrame.document.getElementsByTagName("iframe"); michael@0: let parWindow = null; //curFrame.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: // Check of the curFrame reference is dead michael@0: try { michael@0: frames = curFrame.document.getElementsByTagName("iframe"); michael@0: //Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one. michael@0: //parWindow will refer to the iframe above the nested OOP frame. michael@0: parWindow = curFrame.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils).outerWindowID; michael@0: } catch (e) { michael@0: // We probably have a dead compartment so accessing it is going to make Firefox michael@0: // very upset. Let's now try redirect everything to the top frame even if the michael@0: // user has given us a frame since search doesnt look up. michael@0: msg.json.id = null; michael@0: msg.json.element = null; michael@0: } michael@0: if ((msg.json.id == null) && (msg.json.element == null)) { michael@0: // returning to root frame michael@0: sendSyncMessage("Marionette:switchedToFrame", { frameValue: null }); michael@0: michael@0: curFrame = content; michael@0: if(msg.json.focus == true) { michael@0: curFrame.focus(); michael@0: } michael@0: sandbox = null; michael@0: checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return; michael@0: } michael@0: if (msg.json.element != undefined) { michael@0: if (elementManager.seenItems[msg.json.element] != undefined) { michael@0: let wantedFrame; michael@0: try { michael@0: wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //HTMLIFrameElement michael@0: } michael@0: catch(e) { michael@0: sendError(e.message, e.code, e.stack, command_id); michael@0: } michael@0: for (let i = 0; i < frames.length; i++) { michael@0: // use XPCNativeWrapper to compare elements; see bug 834266 michael@0: if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) { michael@0: curFrame = frames[i]; michael@0: foundFrame = i; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (foundFrame == null) { michael@0: switch(typeof(msg.json.id)) { michael@0: case "string" : michael@0: let foundById = null; michael@0: for (let i = 0; i < frames.length; i++) { michael@0: //give precedence to name michael@0: let frame = frames[i]; michael@0: let name = utils.getElementAttribute(frame, 'name'); michael@0: let id = utils.getElementAttribute(frame, 'id'); michael@0: if (name == msg.json.id) { michael@0: foundFrame = i; michael@0: break; michael@0: } else if ((foundById == null) && (id == msg.json.id)) { michael@0: foundById = i; michael@0: } michael@0: } michael@0: if ((foundFrame == null) && (foundById != null)) { michael@0: foundFrame = foundById; michael@0: curFrame = frames[foundFrame]; michael@0: } michael@0: break; michael@0: case "number": michael@0: if (frames[msg.json.id] != undefined) { michael@0: foundFrame = msg.json.id; michael@0: curFrame = frames[foundFrame]; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: if (foundFrame == null) { michael@0: sendError("Unable to locate frame: " + msg.json.id, 8, null, command_id); michael@0: return; michael@0: } michael@0: michael@0: sandbox = null; michael@0: michael@0: // send a synchronous message to let the server update the currently active michael@0: // frame element (for getActiveFrame) michael@0: let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT']; michael@0: sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue }); michael@0: michael@0: if (curFrame.contentWindow == null) { michael@0: // The frame we want to switch to is a remote (out-of-process) frame; michael@0: // notify our parent to handle the switch. michael@0: curFrame = content; michael@0: sendToServer('Marionette:switchToFrame', {frame: foundFrame, michael@0: win: parWindow, michael@0: command_id: command_id}); michael@0: } michael@0: else { michael@0: curFrame = curFrame.contentWindow; michael@0: if(msg.json.focus == true) { michael@0: curFrame.focus(); michael@0: } michael@0: checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: } michael@0: /** michael@0: * Add a cookie to the document michael@0: */ michael@0: function addCookie(msg) { michael@0: cookie = msg.json.cookie; michael@0: michael@0: if (!cookie.expiry) { michael@0: var date = new Date(); michael@0: var thePresent = new Date(Date.now()); michael@0: date.setYear(thePresent.getFullYear() + 20); michael@0: cookie.expiry = date.getTime() / 1000; // Stored in seconds. michael@0: } michael@0: michael@0: if (!cookie.domain) { michael@0: var location = curFrame.document.location; michael@0: cookie.domain = location.hostname; michael@0: } michael@0: else { michael@0: var currLocation = curFrame.location; michael@0: var currDomain = currLocation.host; michael@0: if (currDomain.indexOf(cookie.domain) == -1) { michael@0: sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id); michael@0: } michael@0: } michael@0: michael@0: // The cookie's domain may include a port. Which is bad. Remove it michael@0: // We'll catch ip6 addresses by mistake. Since no-one uses those michael@0: // this will be okay for now. See Bug 814416 michael@0: if (cookie.domain.match(/:\d+$/)) { michael@0: cookie.domain = cookie.domain.replace(/:\d+$/, ''); michael@0: } michael@0: michael@0: var document = curFrame.document; michael@0: if (!document || !document.contentType.match(/html/i)) { michael@0: sendError('You may only set cookies on html documents', 25, null, msg.json.command_id); michael@0: } michael@0: var cookieManager = Cc['@mozilla.org/cookiemanager;1']. michael@0: getService(Ci.nsICookieManager2); michael@0: cookieManager.add(cookie.domain, cookie.path, cookie.name, cookie.value, michael@0: cookie.secure, false, false, cookie.expiry); michael@0: sendOk(msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Get all cookies for the current domain. michael@0: */ michael@0: function getCookies(msg) { michael@0: var toReturn = []; michael@0: var cookies = getVisibleCookies(curFrame.location); michael@0: for (var i = 0; i < cookies.length; i++) { michael@0: var cookie = cookies[i]; michael@0: var expires = cookie.expires; michael@0: if (expires == 0) { // Session cookie, don't return an expiry. michael@0: expires = null; michael@0: } else if (expires == 1) { // Date before epoch time, cap to epoch. michael@0: expires = 0; michael@0: } michael@0: toReturn.push({ michael@0: 'name': cookie.name, michael@0: 'value': cookie.value, michael@0: 'path': cookie.path, michael@0: 'domain': cookie.host, michael@0: 'secure': cookie.isSecure, michael@0: 'expiry': expires michael@0: }); michael@0: } michael@0: michael@0: sendResponse({value: toReturn}, msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Delete a cookie by name michael@0: */ michael@0: function deleteCookie(msg) { michael@0: var toDelete = msg.json.name; michael@0: var cookieManager = Cc['@mozilla.org/cookiemanager;1']. michael@0: getService(Ci.nsICookieManager); michael@0: michael@0: var cookies = getVisibleCookies(curFrame.location); michael@0: for (var i = 0; i < cookies.length; i++) { michael@0: var cookie = cookies[i]; michael@0: if (cookie.name == toDelete) { michael@0: cookieManager.remove(cookie.host, cookie.name, cookie.path, false); michael@0: } michael@0: } michael@0: michael@0: sendOk(msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Delete all the visibile cookies on a page michael@0: */ michael@0: function deleteAllCookies(msg) { michael@0: let cookieManager = Cc['@mozilla.org/cookiemanager;1']. michael@0: getService(Ci.nsICookieManager); michael@0: let cookies = getVisibleCookies(curFrame.location); michael@0: for (let i = 0; i < cookies.length; i++) { michael@0: let cookie = cookies[i]; michael@0: cookieManager.remove(cookie.host, cookie.name, cookie.path, false); michael@0: } michael@0: sendOk(msg.json.command_id); michael@0: } michael@0: michael@0: /** michael@0: * Get all the visible cookies from a location michael@0: */ michael@0: function getVisibleCookies(location) { michael@0: let results = []; michael@0: let currentPath = location.pathname; michael@0: if (!currentPath) currentPath = '/'; michael@0: let isForCurrentPath = function(aPath) { michael@0: return currentPath.indexOf(aPath) != -1; michael@0: } michael@0: michael@0: let cookieManager = Cc['@mozilla.org/cookiemanager;1']. michael@0: getService(Ci.nsICookieManager); michael@0: let enumerator = cookieManager.enumerator; michael@0: while (enumerator.hasMoreElements()) { michael@0: let cookie = enumerator.getNext().QueryInterface(Ci['nsICookie']); michael@0: michael@0: // Take the hostname and progressively shorten michael@0: let hostname = location.hostname; michael@0: do { michael@0: if ((cookie.host == '.' + hostname || cookie.host == hostname) michael@0: && isForCurrentPath(cookie.path)) { michael@0: results.push(cookie); michael@0: break; michael@0: } michael@0: hostname = hostname.replace(/^.*?\./, ''); michael@0: } while (hostname.indexOf('.') != -1); michael@0: } michael@0: michael@0: return results; michael@0: } michael@0: michael@0: function getAppCacheStatus(msg) { michael@0: sendResponse({ value: curFrame.applicationCache.status }, michael@0: msg.json.command_id); michael@0: } michael@0: michael@0: // emulator callbacks michael@0: let _emu_cb_id = 0; michael@0: let _emu_cbs = {}; michael@0: michael@0: function runEmulatorCmd(cmd, callback) { michael@0: if (callback) { michael@0: _emu_cbs[_emu_cb_id] = callback; michael@0: } michael@0: sendAsyncMessage("Marionette:runEmulatorCmd", {emulator_cmd: cmd, id: _emu_cb_id}); michael@0: _emu_cb_id += 1; michael@0: } michael@0: michael@0: function runEmulatorShell(args, callback) { michael@0: if (callback) { michael@0: _emu_cbs[_emu_cb_id] = callback; michael@0: } michael@0: sendAsyncMessage("Marionette:runEmulatorShell", {emulator_shell: args, id: _emu_cb_id}); michael@0: _emu_cb_id += 1; michael@0: } michael@0: michael@0: function emulatorCmdResult(msg) { michael@0: let message = msg.json; michael@0: if (!sandbox) { michael@0: return; michael@0: } michael@0: let cb = _emu_cbs[message.id]; michael@0: delete _emu_cbs[message.id]; michael@0: if (!cb) { michael@0: return; michael@0: } michael@0: try { michael@0: cb(message.result); michael@0: } michael@0: catch(e) { michael@0: sendError(e.message, e.code, e.stack, -1); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: function importScript(msg) { michael@0: let command_id = msg.json.command_id; michael@0: let file; michael@0: if (importedScripts.exists()) { michael@0: file = FileUtils.openFileOutputStream(importedScripts, michael@0: FileUtils.MODE_APPEND | FileUtils.MODE_WRONLY); michael@0: } michael@0: else { michael@0: //Note: The permission bits here don't actually get set (bug 804563) michael@0: importedScripts.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, michael@0: parseInt("0666", 8)); michael@0: file = FileUtils.openFileOutputStream(importedScripts, michael@0: FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE); michael@0: importedScripts.permissions = parseInt("0666", 8); //actually set permissions michael@0: } michael@0: file.write(msg.json.script, msg.json.script.length); michael@0: file.close(); michael@0: sendOk(command_id); michael@0: } michael@0: michael@0: /** michael@0: * Takes a screen capture of the given web element if id michael@0: * property exists in the message's JSON object, or if null captures michael@0: * the bounding box of the current frame. michael@0: * michael@0: * If given an array of web element references in michael@0: * msg.json.highlights, a red box will be painted around michael@0: * them to highlight their position. michael@0: */ michael@0: function takeScreenshot(msg) { michael@0: let node = null; michael@0: if (msg.json.id) { michael@0: try { michael@0: node = elementManager.getKnownElement(msg.json.id, curFrame) michael@0: } michael@0: catch (e) { michael@0: sendResponse(e.message, e.code, e.stack, msg.json.command_id); michael@0: return; michael@0: } michael@0: } michael@0: else { michael@0: node = curFrame; michael@0: } michael@0: let highlights = msg.json.highlights; michael@0: michael@0: var document = curFrame.document; michael@0: var rect, win, width, height, left, top; michael@0: // node can be either a window or an arbitrary DOM node michael@0: if (node == curFrame) { michael@0: // node is a window michael@0: win = node; michael@0: width = document.body.scrollWidth; michael@0: height = document.body.scrollHeight; michael@0: top = 0; michael@0: left = 0; michael@0: } michael@0: else { michael@0: // node is an arbitrary DOM node michael@0: win = node.ownerDocument.defaultView; michael@0: rect = node.getBoundingClientRect(); michael@0: width = rect.width; michael@0: height = rect.height; michael@0: top = rect.top; michael@0: left = rect.left; michael@0: } michael@0: michael@0: var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", michael@0: "canvas"); michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: var ctx = canvas.getContext("2d"); michael@0: // Draws the DOM contents of the window to the canvas michael@0: ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)"); michael@0: michael@0: // This section is for drawing a red rectangle around each element michael@0: // passed in via the highlights array michael@0: if (highlights) { michael@0: ctx.lineWidth = "2"; michael@0: ctx.strokeStyle = "red"; michael@0: ctx.save(); michael@0: michael@0: for (var i = 0; i < highlights.length; ++i) { michael@0: var elem = elementManager.getKnownElement(highlights[i], curFrame); michael@0: rect = elem.getBoundingClientRect(); michael@0: michael@0: var offsetY = -top; michael@0: var offsetX = -left; michael@0: michael@0: // Draw the rectangle michael@0: ctx.strokeRect(rect.left + offsetX, michael@0: rect.top + offsetY, michael@0: rect.width, michael@0: rect.height); michael@0: } michael@0: } michael@0: michael@0: // Return the Base64 encoded string back to the client so that it michael@0: // can save the file to disk if it is required michael@0: var dataUrl = canvas.toDataURL("image/png", ""); michael@0: var data = dataUrl.substring(dataUrl.indexOf(",") + 1); michael@0: sendResponse({value: data}, msg.json.command_id); michael@0: } michael@0: michael@0: // Call register self when we get loaded michael@0: registerSelf();