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();