Bug 1153832: New dispatch style framework in Marionette listener

Takes advantage of the new dispatching technique introduced in bug
1107706 on the content side.

The patch introduces the framework to write simpler command handlers
in content space, but does not convert all commands in listener.js to
use this.  This can be done gradually, as both techniques are still
compatible.

r=dburns

--HG--
extra : rebase_source : 9ad8846754b86a1acc225c1a9b77a60530e8703b
extra : source : 043a824dd7b749192a8c7ec3f1a8d3ba4d2619d0
This commit is contained in:
Andreas Tolfsen 2015-04-15 12:18:00 +01:00
parent 5ff51a7744
commit 9338edd7c0
5 changed files with 345 additions and 328 deletions

View File

@ -155,8 +155,7 @@ ListenerProxy.prototype.__noSuchMethod__ = function*(name, args) {
let okListener = () => resolve();
let valListener = msg => resolve(msg.json.value);
let errListener = msg => reject(
"error" in msg.objects ? msg.objects.error : msg.json);
let errListener = msg => reject(msg.objects.error);
let handleDialog = function(subject, topic) {
listeners.remove();
@ -2062,7 +2061,7 @@ GeckoDriver.prototype.clickElement = function(cmd, resp) {
// listen for it and then just send an error back. The person making the
// call should be aware something isnt right and handle accordingly
this.addFrameCloseListener("click");
yield this.listener.clickElement({id: id});
yield this.listener.clickElement(id);
break;
}
};
@ -2086,7 +2085,7 @@ GeckoDriver.prototype.getElementAttribute = function(cmd, resp) {
break;
case Context.CONTENT:
resp.value = yield this.listener.getElementAttribute({id: id, name: name});
resp.value = yield this.listener.getElementAttribute(id, name);
break;
}
};
@ -2112,7 +2111,7 @@ GeckoDriver.prototype.getElementText = function(cmd, resp) {
break;
case Context.CONTENT:
resp.value = yield this.listener.getElementText({id: id});
resp.value = yield this.listener.getElementText(id);
break;
}
};
@ -2134,7 +2133,7 @@ GeckoDriver.prototype.getElementTagName = function(cmd, resp) {
break;
case Context.CONTENT:
resp.value = yield this.listener.getElementTagName({id: id});
resp.value = yield this.listener.getElementTagName(id);
break;
}
};
@ -2224,7 +2223,7 @@ GeckoDriver.prototype.isElementEnabled = function(cmd, resp) {
break;
case Context.CONTENT:
resp.value = yield this.listener.isElementEnabled({id: id});
resp.value = yield this.listener.isElementEnabled(id);
break;
}
},
@ -2270,7 +2269,7 @@ GeckoDriver.prototype.getElementSize = function(cmd, resp) {
break;
case Context.CONTENT:
resp.value = yield this.listener.getElementSize({id: id});
resp.value = yield this.listener.getElementSize(id);
break;
}
};
@ -2292,7 +2291,7 @@ GeckoDriver.prototype.getElementRect = function(cmd, resp) {
break;
case Context.CONTENT:
resp.value = yield this.listener.getElementRect({id: id});
resp.value = yield this.listener.getElementRect(id);
break;
}
};

View File

@ -1,8 +1,11 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
let {utils: Cu} = Components;
Cu.import("chrome://marionette/content/error.js");
/**
* The ElementManager manages DOM references and interactions with elements.
* According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the
@ -28,8 +31,8 @@ this.EXPORTED_SYMBOLS = [
const DOCUMENT_POSITION_DISCONNECTED = 1;
let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Components.interfaces.nsIUUIDGenerator);
const uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Components.interfaces.nsIUUIDGenerator);
this.CLASS_NAME = "class name";
this.SELECTOR = "css selector";
@ -42,12 +45,6 @@ this.XPATH = "xpath";
this.ANON= "anon";
this.ANON_ATTRIBUTE = "anon attribute";
function ElementException(msg, num, stack) {
this.message = msg;
this.code = num;
this.stack = stack;
}
this.Accessibility = function Accessibility() {
// A flag indicating whether the accessibility issue should be logged or cause
// an exception. Default: log to stdout.
@ -185,7 +182,7 @@ Accessibility.prototype = {
return;
}
if (this.strict) {
throw new ElementException(message, 56, null);
throw new ElementNotAccessibleError(message);
}
dump(Date.now() + " Marionette: " + message);
}
@ -224,19 +221,17 @@ ElementManager.prototype = {
let foundEl = null;
try {
foundEl = this.seenItems[i].get();
}
catch(e) {}
} catch (e) {}
if (foundEl) {
if (XPCNativeWrapper(foundEl) == XPCNativeWrapper(element)) {
return i;
}
}
else {
//cleanup reference to GC'd element
} else {
// cleanup reference to GC'd element
delete this.seenItems[i];
}
}
var id = uuidGen.generateUUID().toString();
let id = uuidGen.generateUUID().toString();
this.seenItems[id] = Components.utils.getWeakReference(element);
return id;
},
@ -255,7 +250,7 @@ ElementManager.prototype = {
getKnownElement: function EM_getKnownElement(id, win) {
let el = this.seenItems[id];
if (!el) {
throw new ElementException("Element has not been seen before. Id given was " + id, 17, null);
throw new JavaScriptError("Element has not been seen before. Id given was " + id);
}
try {
el = el.get();
@ -270,8 +265,9 @@ ElementManager.prototype = {
!(XPCNativeWrapper(el).ownerDocument == wrappedWin.document) ||
(XPCNativeWrapper(el).compareDocumentPosition(wrappedWin.document.documentElement) &
DOCUMENT_POSITION_DISCONNECTED)) {
throw new ElementException("The element reference is stale. Either the element " +
"is no longer attached to the DOM or the page has been refreshed.", 10, null);
throw new StaleElementReferenceError(
"The element reference is stale. Either the element " +
"is no longer attached to the DOM or the page has been refreshed.");
}
return el;
},
@ -369,8 +365,9 @@ ElementManager.prototype = {
args.hasOwnProperty(this.w3cElementKey))) {
let elementUniqueIdentifier = args[this.w3cElementKey] ? args[this.w3cElementKey] : args[this.elementKey];
converted = this.getKnownElement(elementUniqueIdentifier, win);
if (converted == null)
throw new ElementException("Unknown element: " + elementUniqueIdentifier, 500, null);
if (converted == null) {
throw new WebDriverError(`Unknown element: ${elementUniqueIdentifier}`);
}
}
else {
converted = {};
@ -443,7 +440,7 @@ ElementManager.prototype = {
let startNode = (values.element != undefined) ?
this.getKnownElement(values.element, win) : win.document;
if (this.elementStrategies.indexOf(values.using) < 0) {
throw new ElementException("No such strategy.", 32, null);
throw new InvalidSelectorError(`No such strategy: ${values.using}`);
}
let found = all ? this.findElements(values.using, values.value, win.document, startNode) :
this.findElement(values.using, values.value, win.document, startNode);
@ -461,7 +458,7 @@ ElementManager.prototype = {
} else if (values.using == ANON_ATTRIBUTE) {
message = "Unable to locate anonymous element: " + JSON.stringify(values.value);
}
on_error({message: message, code: 7}, command_id);
on_error(new NoSuchElementError(message), command_id);
}
} else {
values.time = startTime;
@ -594,7 +591,7 @@ ElementManager.prototype = {
element = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
break;
default:
throw new ElementException("No such strategy", 500, null);
throw new WebDriverError("No such strategy");
}
return element;
},
@ -661,7 +658,7 @@ ElementManager.prototype = {
}
break;
default:
throw new ElementException("No such strategy", 500, null);
throw new WebDriverError("No such strategy");
}
return elements;
},

View File

@ -4,14 +4,16 @@
"use strict";
const {utils: Cu} = Components;
const {results: Cr, utils: Cu} = Components;
const errors = [
"ElementNotAccessibleError",
"ElementNotVisibleError",
"FrameSendFailureError",
"FrameSendNotInitializedError",
"IllegalArgumentError",
"InvalidElementStateError",
"InvalidSelectorError",
"JavaScriptError",
"NoAlertOpenError",
"NoSuchElementError",
@ -19,6 +21,7 @@ const errors = [
"NoSuchWindowError",
"ScriptTimeoutError",
"SessionNotCreatedError",
"StaleElementReferenceError",
"TimeoutError",
"UnknownCommandError",
"UnknownError",
@ -28,6 +31,26 @@ const errors = [
this.EXPORTED_SYMBOLS = ["error"].concat(errors);
// Because XPCOM is a cesspool of undocumented odd behaviour,
// Object.getPrototypeOf(err) causes another exception if err is an XPCOM
// exception, and cannot be used to determine if err is a prototypal Error.
//
// Consequently we need to check for properties in its prototypal chain
// (using in, instead of err.hasOwnProperty because that causes other
// issues).
//
// Since the input is arbitrary it might _not_ be an Error, and can as
// such be an object with a "result" property without it being considered to
// be an exception. The solution is to build a lookup table of XPCOM
// exceptions from Components.results and check if the value of err#results
// is in that table.
const XPCOM_EXCEPTIONS = [];
{
for (let prop in Cr) {
XPCOM_EXCEPTIONS.push(Cr[prop]);
}
}
this.error = {};
error.toJSON = function(err) {
@ -38,11 +61,6 @@ error.toJSON = function(err) {
};
};
/**
* Gets WebDriver error by its Selenium status code number.
*/
error.byCode = n => lookup.get(n);
/**
* Determines if the given status code is successful.
*/
@ -65,18 +83,19 @@ let isOldStyleError = function(obj) {
* Prefer using this over using instanceof since the Error prototype
* isn't unique across browsers, and XPCOM exceptions are special
* snowflakes.
*
* @param {*} val
* Any value that should be undergo the test for errorness.
* @return {boolean}
* True if error, false otherwise.
*/
error.isError = function(obj) {
if (obj === null || typeof obj != "object") {
error.isError = function(val) {
if (val === null || typeof val != "object") {
return false;
// XPCOM exception.
// Object.getPrototypeOf(obj).result throws error,
// consequently we must do the check for properties in its
// prototypal chain (using in, instead of obj.hasOwnProperty) here.
} else if ("result" in obj) {
} else if ("result" in val && val.result in XPCOM_EXCEPTIONS) {
return true;
} else {
return Object.getPrototypeOf(obj) == "Error" || isOldStyleError(obj);
return Object.getPrototypeOf(val) == "Error" || isOldStyleError(val);
}
};
@ -130,6 +149,14 @@ this.WebDriverError = function(msg) {
};
WebDriverError.prototype = Object.create(Error.prototype);
this.ElementNotAccessibleError = function(msg) {
WebDriverError.call(this, msg);
this.name = "ElementNotAccessibleError";
this.status = "element not accessible";
this.code = 56;
};
ElementNotAccessibleError.prototype = Object.create(WebDriverError.prototype);
this.ElementNotVisibleError = function(msg) {
WebDriverError.call(this, msg);
this.name = "ElementNotVisibleError";
@ -176,6 +203,14 @@ this.InvalidElementStateError = function(msg) {
};
InvalidElementStateError.prototype = Object.create(WebDriverError.prototype);
this.InvalidSelectorError = function(msg) {
WebDriverError.call(this, msg);
this.name = "InvalidSelectorError";
this.status = "invalid selector";
this.code = 32;
};
InvalidSelectorError.prototype = Object.create(WebDriverError.prototype);
/**
* Creates an error message for a JavaScript error thrown during
* executeScript or executeAsyncScript.
@ -270,9 +305,17 @@ this.SessionNotCreatedError = function(msg) {
this.status = "session not created";
// should be 33 to match Selenium
this.code = 71;
}
};
SessionNotCreatedError.prototype = Object.create(WebDriverError.prototype);
this.StaleElementReferenceError = function(msg) {
WebDriverError.call(this, msg);
this.name = "StaleElementReferenceError";
this.status = "stale element reference";
this.code = 10;
};
StaleElementReferenceError.prototype = Object.create(WebDriverError.prototype);
this.TimeoutError = function(msg) {
WebDriverError.call(this, msg);
this.name = "TimeoutError";
@ -304,24 +347,3 @@ this.UnsupportedOperationError = function(msg) {
this.code = 405;
};
UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
const errorObjs = [
this.ElementNotVisibleError,
this.FrameSendFailureError,
this.FrameSendNotInitializedError,
this.IllegalArgumentError,
this.InvalidElementStateError,
this.JavaScriptError,
this.NoAlertOpenError,
this.NoSuchElementError,
this.NoSuchFrameError,
this.NoSuchWindowError,
this.ScriptTimeoutError,
this.SessionNotCreatedError,
this.TimeoutError,
this.UnknownCommandError,
this.UnknownError,
this.UnsupportedOperationError,
this.WebDriverError,
];
const lookup = new Map(errorObjs.map(err => [new err().code, err]));

View File

@ -15,6 +15,7 @@ loader.loadSubScript("chrome://marionette/content/simpletest.js");
loader.loadSubScript("chrome://marionette/content/common.js");
loader.loadSubScript("chrome://marionette/content/actions.js");
Cu.import("chrome://marionette/content/elements.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -93,7 +94,7 @@ let modalHandler = function() {
* If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
*/
function registerSelf() {
let msg = {value: winUtil.outerWindowID}
let msg = {value: winUtil.outerWindowID};
// register will have the ID and a boolean describing if this is the main process or not
let register = sendSyncMessage("Marionette:register", msg);
@ -146,6 +147,28 @@ function emitTouchEventForIFrame(message) {
message.force, 90);
}
function dispatch(fn) {
return function(msg) {
let id = msg.json.command_id;
try {
let rv;
if (typeof msg.json == "undefined" || msg.json instanceof Array) {
rv = fn.apply(null, msg.json);
} else {
rv = fn(msg.json);
}
if (typeof rv == "undefined") {
sendOk(id);
} else {
sendResponse({value: rv}, id);
}
} catch (e) {
sendError(e, id);
}
};
}
/**
* Add a message listener that's tied to our listenerId.
*/
@ -160,6 +183,15 @@ function removeMessageListenerId(messageName, handler) {
removeMessageListener(messageName + listenerId, handler);
}
let getElementSizeFn = dispatch(getElementSize);
let getActiveElementFn = dispatch(getActiveElement);
let clickElementFn = dispatch(clickElement);
let getElementAttributeFn = dispatch(getElementAttribute);
let getElementTextFn = dispatch(getElementText);
let getElementTagNameFn = dispatch(getElementTagName);
let getElementRectFn = dispatch(getElementRect);
let isElementEnabledFn = dispatch(isElementEnabled);
/**
* Start all message listeners
*/
@ -182,17 +214,17 @@ function startListeners() {
addMessageListenerId("Marionette:refresh", refresh);
addMessageListenerId("Marionette:findElementContent", findElementContent);
addMessageListenerId("Marionette:findElementsContent", findElementsContent);
addMessageListenerId("Marionette:getActiveElement", getActiveElement);
addMessageListenerId("Marionette:clickElement", clickElement);
addMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
addMessageListenerId("Marionette:getElementText", getElementText);
addMessageListenerId("Marionette:getElementTagName", getElementTagName);
addMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
addMessageListenerId("Marionette:clickElement", clickElementFn);
addMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
addMessageListenerId("Marionette:getElementText", getElementTextFn);
addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
addMessageListenerId("Marionette:submitElement", submitElement);
addMessageListenerId("Marionette:getElementSize", getElementSize);
addMessageListenerId("Marionette:getElementRect", getElementRect);
addMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
addMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated
addMessageListenerId("Marionette:getElementRect", getElementRectFn);
addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
addMessageListenerId("Marionette:isElementSelected", isElementSelected);
addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
addMessageListenerId("Marionette:getElementLocation", getElementLocation); //deprecated
@ -287,17 +319,17 @@ function deleteSession(msg) {
removeMessageListenerId("Marionette:refresh", refresh);
removeMessageListenerId("Marionette:findElementContent", findElementContent);
removeMessageListenerId("Marionette:findElementsContent", findElementsContent);
removeMessageListenerId("Marionette:getActiveElement", getActiveElement);
removeMessageListenerId("Marionette:clickElement", clickElement);
removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
removeMessageListenerId("Marionette:getElementText", getElementText);
removeMessageListenerId("Marionette:getElementTagName", getElementTagName);
removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
removeMessageListenerId("Marionette:clickElement", clickElementFn);
removeMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
removeMessageListenerId("Marionette:getElementText", getElementTextFn);
removeMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
removeMessageListenerId("Marionette:submitElement", submitElement);
removeMessageListenerId("Marionette:getElementSize", getElementSize); //deprecated
removeMessageListenerId("Marionette:getElementRect", getElementRect);
removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
removeMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated
removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
removeMessageListenerId("Marionette:isElementSelected", isElementSelected);
removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
removeMessageListenerId("Marionette:getElementLocation", getElementLocation);
@ -331,40 +363,42 @@ function deleteSession(msg) {
/**
* Generic method to send a message to the server
*/
function sendToServer(msg, value, command_id) {
if (command_id) {
value.command_id = command_id;
function sendToServer(name, data, objs, id) {
if (!data) {
data = {}
}
sendAsyncMessage(msg, value);
if (id) {
data.command_id = id;
}
sendAsyncMessage(name, data, objs);
}
/**
* Send response back to server
*/
function sendResponse(value, command_id) {
sendToServer("Marionette:done", value, command_id);
sendToServer("Marionette:done", value, null, command_id);
}
/**
* Send ack back to server
*/
function sendOk(command_id) {
sendToServer("Marionette:ok", {}, command_id);
sendToServer("Marionette:ok", null, null, command_id);
}
/**
* Send log message to server
*/
function sendLog(msg) {
sendToServer("Marionette:log", { message: msg });
sendToServer("Marionette:log", {message: msg});
}
/**
* Send error message to server
*/
function sendError(msg, code, stack, cmdId) {
let payload = {message: msg, code: code, stack: stack};
sendToServer("Marionette:error", payload, cmdId);
function sendError(err, cmdId) {
sendToServer("Marionette:error", null, {error: err}, cmdId);
}
/**
@ -458,8 +492,8 @@ function createExecuteContentSandbox(aWindow, timeout) {
});
}
sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) {
if (commandId == asyncTestCommandId) {
sandbox.asyncComplete = function(obj, id) {
if (id == asyncTestCommandId) {
curFrame.removeEventListener("unload", onunload, false);
curFrame.clearTimeout(asyncTestTimeoutId);
@ -467,24 +501,19 @@ function createExecuteContentSandbox(aWindow, timeout) {
curFrame.clearTimeout(inactivityTimeoutId);
}
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
if (status == 0){
if (error.isError(obj)) {
sendError(obj, id);
} else {
if (Object.keys(_emu_cbs).length) {
_emu_cbs = {};
sendError("Emulator callback still pending when finish() called",
500, null, commandId);
sendError(new WebDriverError("Emulator callback still pending when finish() called"), id);
} else {
sendResponse({value: elementManager.wrapValue(obj)}, id);
}
else {
sendResponse({value: elementManager.wrapValue(value), status: status},
commandId);
}
}
else {
sendError(value, status, stack, commandId);
}
asyncTestRunning = false;
@ -495,33 +524,32 @@ function createExecuteContentSandbox(aWindow, timeout) {
};
sandbox.finish = function sandbox_finish() {
if (asyncTestRunning) {
sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId);
sandbox.asyncComplete(marionette.generate_results(), sandbox.asyncTestCommandId);
} else {
return marionette.generate_results();
}
};
sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) {
return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId);
};
sandbox.marionetteScriptFinished = val =>
sandbox.asyncComplete(val, sandbox.asyncTestCommandId);
return sandbox;
}
/**
* Execute the given script either as a function body (executeScript)
* or directly (for 'mochitest' like JS Marionette tests)
* or directly (for mochitest like JS Marionette tests).
*/
function executeScript(msg, directInject) {
// Set up inactivity timeout.
if (msg.json.inactivityTimeout) {
let setTimer = function() {
inactivityTimeoutId = curFrame.setTimeout(function() {
sendError('timed out due to inactivity', 28, null, asyncTestCommandId);
inactivityTimeoutId = curFrame.setTimeout(function() {
sendError(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId);
}, msg.json.inactivityTimeout);
};
setTimer();
heartbeatCallback = function resetInactivityTimeout() {
heartbeatCallback = function() {
curFrame.clearTimeout(inactivityTimeoutId);
setTimer();
};
@ -534,11 +562,10 @@ function executeScript(msg, directInject) {
sandbox = createExecuteContentSandbox(curFrame,
msg.json.timeout);
if (!sandbox) {
sendError("Could not create sandbox!", 500, null, asyncTestCommandId);
sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId);
return;
}
}
else {
} else {
sandbox.asyncTestCommandId = asyncTestCommandId;
}
@ -558,7 +585,7 @@ function executeScript(msg, directInject) {
marionetteLogObj.clearLogs();
if (res == undefined || res.passed == undefined) {
sendError("Marionette.finish() not called", 17, null, asyncTestCommandId);
sendError(new JavaScriptError("Marionette.finish() not called"), asyncTestCommandId);
}
else {
sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
@ -568,9 +595,8 @@ function executeScript(msg, directInject) {
try {
sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
msg.json.args, curFrame), sandbox, { wrapReflectors: true });
}
catch(e) {
sendError(e.message, e.code, e.stack, asyncTestCommandId);
} catch (e) {
sendError(e, asyncTestCommandId);
return;
}
@ -590,15 +616,14 @@ function executeScript(msg, directInject) {
marionetteLogObj.clearLogs();
sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
}
}
catch (e) {
// 17 = JavascriptException
let error = createStackMessage(e,
"execute_script",
msg.json.filename,
msg.json.line,
script);
sendError(error[0], 17, error[1], asyncTestCommandId);
} catch (e) {
let err = new JavaScriptError(
e,
"execute_script",
msg.json.filename,
msg.json.line,
script);
sendError(err, asyncTestCommandId);
}
}
@ -642,12 +667,12 @@ function executeWithCallback(msg, useFinish) {
if (msg.json.inactivityTimeout) {
let setTimer = function() {
inactivityTimeoutId = curFrame.setTimeout(function() {
sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId);
sandbox.asyncComplete(new ScriptTimeout("timed out due to inactivity"), asyncTestCommandId);
}, msg.json.inactivityTimeout);
};
setTimer();
heartbeatCallback = function resetInactivityTimeout() {
heartbeatCallback = function() {
curFrame.clearTimeout(inactivityTimeoutId);
setTimer();
};
@ -657,7 +682,7 @@ function executeWithCallback(msg, useFinish) {
asyncTestCommandId = msg.json.command_id;
onunload = function() {
sendError("unload was called", 17, null, asyncTestCommandId);
sendError(new JavaScriptError("unload was called"), asyncTestCommandId);
};
curFrame.addEventListener("unload", onunload, false);
@ -665,7 +690,7 @@ function executeWithCallback(msg, useFinish) {
sandbox = createExecuteContentSandbox(curFrame,
msg.json.timeout);
if (!sandbox) {
sendError("Could not create sandbox!", 17, null, asyncTestCommandId);
sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId);
return;
}
}
@ -680,19 +705,19 @@ function executeWithCallback(msg, useFinish) {
// http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
// We'll stay compatible with the Selenium code.
asyncTestTimeoutId = curFrame.setTimeout(function() {
sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId);
sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId);
}, msg.json.timeout);
originalOnError = curFrame.onerror;
curFrame.onerror = function errHandler(errMsg, url, line) {
sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId);
curFrame.onerror = function errHandler(msg, url, line) {
sandbox.asyncComplete(new JavaScriptError(msg + "@" + url + ", line " + line), asyncTestCommandId);
curFrame.onerror = originalOnError;
};
let scriptSrc;
if (useFinish) {
if (msg.json.timeout == null || msg.json.timeout == 0) {
sendError("Please set a timeout", 21, null, asyncTestCommandId);
sendError(new TimeoutError("Please set a timeout"), asyncTestCommandId);
}
scriptSrc = script;
}
@ -700,9 +725,8 @@ function executeWithCallback(msg, useFinish) {
try {
sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
msg.json.args, curFrame), sandbox, { wrapReflectors: true });
}
catch(e) {
sendError(e.message, e.code, e.stack, asyncTestCommandId);
} catch (e) {
sendError(e, asyncTestCommandId);
return;
}
@ -723,13 +747,13 @@ function executeWithCallback(msg, useFinish) {
}
Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0);
} catch (e) {
// 17 = JavascriptException
let error = createStackMessage(e,
"execute_async_script",
msg.json.filename,
msg.json.line,
scriptSrc);
sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId);
let err = new JavaScriptError(
e,
"execute_async_script",
msg.json.filename,
msg.json.line,
scriptSrc);
sandbox.asyncComplete(err, asyncTestCommandId);
}
}
@ -856,7 +880,7 @@ function singleTap(msg) {
let visible = checkVisible(el, msg.json.corx, msg.json.cory);
checkVisibleAccessibility(acc, visible);
if (!visible) {
sendError("Element is not currently visible and may not be manipulated", 11, null, command_id);
sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id);
return;
}
checkActionableAccessibility(acc);
@ -871,10 +895,9 @@ function singleTap(msg) {
emitTouchEvent('touchend', touch);
}
actions.mouseTap(el.ownerDocument, c.x, c.y);
sendOk(msg.json.command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, msg.json.command_id);
sendOk(command_id);
} catch (e) {
sendError(e, command_id);
}
}
@ -959,12 +982,8 @@ function actionChain(msg) {
let touchId = msg.json.nextId;
let callbacks = {};
callbacks.onSuccess = (value) => {
sendResponse(value, command_id);
};
callbacks.onError = (message, code, trace) => {
sendError(message, code, trace, msg.json.command_id);
};
callbacks.onSuccess = value => sendResponse(value, command_id);
callbacks.onError = err => sendError(err, command_id);
let touchProvider = {};
touchProvider.createATouch = createATouch;
@ -979,7 +998,7 @@ function actionChain(msg) {
callbacks,
touchProvider);
} catch (e) {
sendError(e.message, e.code, e.stack, command_id);
sendError(e, command_id);
}
}
@ -1144,9 +1163,8 @@ function multiAction(msg) {
// pendingTouches keeps track of current touches that's on the screen
let pendingTouches = [];
setDispatch(concurrentEvent, pendingTouches, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, msg.json.command_id);
} catch (e) {
sendError(e, command_id);
}
}
@ -1178,7 +1196,7 @@ function pollForReadyState(msg, start, callback) {
!curFrame.document.baseURI.startsWith(url)) {
// We have reached an error url without requesting it.
callback();
sendError("Error loading page", 13, null, command_id);
sendError(new UnknownError("Error loading page"), command_id);
} else if (curFrame.document.readyState == "interactive" &&
curFrame.document.baseURI.startsWith("about:")) {
callback();
@ -1186,11 +1204,9 @@ function pollForReadyState(msg, start, callback) {
} else {
navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
}
}
else {
} else {
callback();
sendError("Error loading page, timed out (checkLoad)", 21, null,
command_id);
sendError(new TimeoutError("Error loading page, timed out (checkLoad)"), command_id);
}
}
checkLoad();
@ -1220,8 +1236,7 @@ function get(msg) {
function timerFunc() {
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
sendError("Error loading page, timed out (onDOMContentLoaded)", 21,
null, msg.json.command_id);
sendError(new TimeoutError("Error loading page, timed out (onDOMContentLoaded)"), msg.json.command_id);
}
if (msg.json.pageTimeout != null) {
navTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
@ -1306,13 +1321,12 @@ function refresh(msg) {
function findElementContent(msg) {
let command_id = msg.json.command_id;
try {
let on_success = function(el, cmd_id) { sendResponse({value: el}, cmd_id) };
let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
let onSuccess = (el, id) => sendResponse({value: el}, id);
let onError = (err, id) => sendError(err, id);
elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
false /* all */, on_success, on_error, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
false /* all */, onSuccess, onError, command_id);
} catch (e) {
sendError(e, command_id);
}
}
@ -1322,97 +1336,90 @@ function findElementContent(msg) {
function findElementsContent(msg) {
let command_id = msg.json.command_id;
try {
let on_success = function(els, cmd_id) { sendResponse({value: els}, cmd_id); };
let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
let onSuccess = (els, id) => sendResponse({value: els}, id);
let onError = (err, id) => sendError(err, id);
elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
true /* all */, on_success, on_error, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
true /* all */, onSuccess, onError, command_id);
} catch (e) {
sendError(e, command_id);
}
}
/**
* Find and return the active element on the page
* Find and return the active element on the page.
*
* @return {WebElement}
* Reference to web element.
*/
function getActiveElement(msg) {
let command_id = msg.json.command_id;
var element = curFrame.document.activeElement;
var id = elementManager.addToKnownElements(element);
sendResponse({value: id}, command_id);
function getActiveElement() {
let el = curFrame.document.activeElement;
return elementManager.addToKnownElements(el);
}
/**
* Send click event to element
* Send click event to element.
*
* @param {WebElement} id
* Reference to the web element to click.
*/
function clickElement(msg) {
let command_id = msg.json.command_id;
let el;
try {
el = elementManager.getKnownElement(msg.json.id, curFrame);
let acc = accessibility.getAccessibleObject(el, true);
let visible = checkVisible(el);
checkVisibleAccessibility(acc, visible);
if (visible) {
checkActionableAccessibility(acc);
if (utils.isElementEnabled(el)) {
utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView)
}
else {
sendError("Element is not Enabled", 12, null, command_id)
}
}
else {
sendError("Element is not visible", 11, null, command_id)
}
sendOk(command_id);
function clickElement(id) {
let el = elementManager.getKnownElement(id, curFrame);
let acc = accessibility.getAccessibleObject(el, true);
let visible = checkVisible(el);
checkVisibleAccessibility(acc, visible);
if (!visible) {
throw new ElementNotVisibleError("Element is not visible");
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
checkActionableAccessibility(acc);
if (utils.isElementEnabled(el)) {
utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView);
} else {
throw new InvalidElementStateError("Element is not Enabled");
}
}
/**
* Get a given attribute of an element
* Get a given attribute of an element.
*
* @param {WebElement} id
* Reference to the web element to get the attribute of.
* @param {string} name
* Name of the attribute.
*
* @return {string}
* The value of the attribute.
*/
function getElementAttribute(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.id, curFrame);
sendResponse({value: utils.getElementAttribute(el, msg.json.name)},
command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
}
function getElementAttribute(id, name) {
let el = elementManager.getKnownElement(id, curFrame);
return utils.getElementAttribute(el, name);
}
/**
* Get the text of this element. This includes text from child elements.
*
* @param {WebElement} id
* Reference to web element.
*
* @return {string}
* Text of element.
*/
function getElementText(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.id, curFrame);
sendResponse({value: utils.getElementText(el)}, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
}
function getElementText(id) {
let el = elementManager.getKnownElement(id, curFrame);
return utils.getElementText(el);
}
/**
* Get the tag name of an element.
*
* @param {WebElement} id
* Reference to web element.
*
* @return {string}
* Tag name of element.
*/
function getElementTagName(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.id, curFrame);
sendResponse({value: el.tagName.toLowerCase()}, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
}
function getElementTagName(id) {
let el = elementManager.getKnownElement(id, curFrame);
return el.tagName.toLowerCase();
}
/**
@ -1425,9 +1432,8 @@ function isElementDisplayed(msg) {
let displayed = utils.isElementDisplayed(el);
checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed);
sendResponse({value: displayed}, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
} catch (e) {
sendError(e, command_id);
}
}
@ -1446,9 +1452,8 @@ function getElementValueOfCssProperty(msg){
let el = elementManager.getKnownElement(msg.json.id, curFrame);
sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)},
command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
} catch (e) {
sendError(e, command_id);
}
}
@ -1467,67 +1472,63 @@ function submitElement (msg) {
if (el.tagName && el.tagName.toLowerCase() == 'form') {
el.submit();
sendOk(command_id);
} else {
sendError(new NoSuchElementError("Element is not a form element or in a form"), command_id);
}
else {
sendError("Element is not a form element or in a form", 7, null, command_id);
}
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
} catch (e) {
sendError(e, command_id);
}
}
/**
* Get the size of the element and return it
* Get the size of the element.
*
* @param {WebElement} id
* Web element reference.
*
* @return {Object.<string, number>}
* The width/height dimensions of th element.
*/
function getElementSize(msg){
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.id, curFrame);
let clientRect = el.getBoundingClientRect();
sendResponse({value: {width: clientRect.width, height: clientRect.height}},
command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
}
function getElementSize(id) {
let el = elementManager.getKnownElement(id, curFrame);
let clientRect = el.getBoundingClientRect();
return {width: clientRect.width, height: clientRect.height};
}
/**
* Get the size of the element and return it
* Get the size of the element.
*
* @param {WebElement} id
* Reference to web element.
*
* @return {Object.<string, number>}
* The x, y, width, and height properties of the element.
*/
function getElementRect(msg){
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.id, curFrame);
let clientRect = el.getBoundingClientRect();
sendResponse({value: {x: clientRect.x + curFrame.pageXOffset,
y: clientRect.y + curFrame.pageYOffset,
width: clientRect.width,
height: clientRect.height}},
command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
}
function getElementRect(id) {
let el = elementManager.getKnownElement(id, curFrame);
let clientRect = el.getBoundingClientRect();
return {
x: clientRect.x + curFrame.pageXOffset,
y: clientRect.y + curFrame.pageYOffset,
width: clientRect.width,
height: clientRect.height
};
}
/**
* Check if element is enabled
* Check if element is enabled.
*
* @param {WebElement} id
* Reference to web element.
*
* @return {boolean}
* True if enabled, false otherwise.
*/
function isElementEnabled(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.id, curFrame);
let enabled = utils.isElementEnabled(el);
checkEnabledStateAccessibility(accessibility.getAccessibleObject(el),
enabled);
sendResponse({value: enabled}, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
}
function isElementEnabled(id) {
let el = elementManager.getKnownElement(id, curFrame);
let enabled = utils.isElementEnabled(el);
checkEnabledStateAccessibility(accessibility.getAccessibleObject(el), enabled);
return enabled;
}
/**
@ -1538,9 +1539,8 @@ function isElementSelected(msg) {
try {
let el = elementManager.getKnownElement(msg.json.id, curFrame);
sendResponse({value: utils.isElementSelected(el)}, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
} catch (e) {
sendError(e, command_id);
}
}
@ -1567,7 +1567,7 @@ function sendKeysToElement(msg) {
file = new File(p);
} catch (e) {
let err = new IllegalArgumentError(`File not found: ${val}`);
sendError(err.message, err.code, err.stack, command_id);
sendError(err, command_id);
return;
}
fs.push(file);
@ -1598,9 +1598,8 @@ function getElementLocation(msg) {
location.y = rect.top;
sendResponse({value: location}, command_id);
}
catch (e) {
sendError(e.message, e.code, e.stack, command_id);
} catch (e) {
sendError(e, command_id);
}
}
@ -1618,7 +1617,7 @@ function clearElement(msg) {
}
sendOk(command_id);
} catch (e) {
sendError(e.message, e.code, e.stack, command_id);
sendError(e, command_id);
}
}
@ -1633,9 +1632,9 @@ function switchToFrame(msg) {
if (curFrame.document.readyState == "complete") {
sendOk(command_id);
return;
}
else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) {
sendError("Error loading page", 13, null, command_id);
} else if (curFrame.document.readyState == "interactive" &&
errorRegex.exec(curFrame.document.baseURI)) {
sendError(new UnknownError("Error loading page"), command_id);
return;
}
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
@ -1675,9 +1674,8 @@ function switchToFrame(msg) {
let wantedFrame;
try {
wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //Frame Element
}
catch(e) {
sendError(e.message, e.code, e.stack, command_id);
} catch (e) {
sendError(e, command_id);
}
if (frames.length > 0) {
@ -1735,8 +1733,9 @@ function switchToFrame(msg) {
}
}
}
if (foundFrame === null) {
sendError("Unable to locate frame: " + (msg.json.id || msg.json.element), 8, null, command_id);
sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id);
return true;
}
@ -1777,12 +1776,11 @@ function addCookie(msg) {
if (!cookie.domain) {
var location = curFrame.document.location;
cookie.domain = location.hostname;
}
else {
} else {
var currLocation = curFrame.location;
var currDomain = currLocation.host;
if (currDomain.indexOf(cookie.domain) == -1) {
sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id);
sendError(new InvalidCookieDomainError("You may only set cookies for the current domain"), msg.json.command_id);
}
}
@ -1795,12 +1793,12 @@ function addCookie(msg) {
var document = curFrame.document;
if (!document || !document.contentType.match(/html/i)) {
sendError('You may only set cookies on html documents', 25, null, msg.json.command_id);
sendError(new UnableToSetCookie("You may only set cookies on html documents"), msg.json.command_id);
}
let added = sendSyncMessage("Marionette:addCookie", {value: cookie});
if (added[0] !== true) {
sendError("Error setting cookie", 13, null, msg.json.command_id);
sendError(new UnknownError("Error setting cookie"), msg.json.command_id);
return;
}
sendOk(msg.json.command_id);
@ -1841,7 +1839,7 @@ function deleteCookie(msg) {
if (cookie.name == toDelete) {
let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
if (deleted[0] !== true) {
sendError("Could not delete cookie: " + msg.json.name, 13, null, msg.json.command_id);
sendError(new UnknownError("Could not delete cookie: " + msg.json.name), msg.json.command_id);
return;
}
}
@ -1858,7 +1856,7 @@ function deleteAllCookies(msg) {
for (let cookie of cookies) {
let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
if (!deleted[0]) {
sendError("Could not delete cookie: " + JSON.stringify(cookie), 13, null, msg.json.command_id);
sendError(new UnknownError("Could not delete cookie: " + JSON.stringify(cookie)), msg.json.command_id);
return;
}
}
@ -1912,9 +1910,8 @@ function emulatorCmdResult(msg) {
}
try {
cb(message.result);
}
catch(e) {
sendError(e.message, e.code, e.stack, -1);
} catch (e) {
sendError(e, -1);
return;
}
}

View File

@ -17,8 +17,11 @@
*/
let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("chrome://marionette/content/error.js");
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
.getService(Ci.mozIJSSubScriptLoader);
let utils = {};
loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
@ -138,8 +141,7 @@ function sendKeysToElement (document, element, keysToSend, successCallback, erro
sendSingleKey(c, modifiers, document);
}
successCallback(command_id);
}
else {
errorCallback("Element is not visible", 11, null, command_id);
} else {
errorCallback(new ElementNotVisibleError("Element is not visible"), command_id);
}
};