--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -939,22 +939,28 @@ class Marionette(object):
let secMan = Services.scriptSecurityManager;
let attrs = {appId: perm.appId, inIsolatedMozBrowser: perm.isInIsolatedMozBrowserElement};
let principal = secMan.createCodebasePrincipal(Services.io.newURI(perm.url, null, null),
attrs);
Services.perms.addFromPrincipal(principal, perm.type, perm.action);
return true;
""", script_args=[perm])
- with self.using_context('content'):
+ with self.using_context("content"):
self.execute_async_script("""
- waitFor(marionetteScriptFinished, function() {
- return window.wrappedJSObject.permChanged;
- });
- """, sandbox='system')
+ let start = new Date();
+ let end = new Date(start.valueOf() + 5000);
+ let wait = function() {
+ let now = new Date();
+ if (window.wrappedJSObject.permChanged || end >= now) {
+ marionetteScriptFinished();
+ }
+ };
+ window.setTimeout(wait, 100);
+ """, sandbox="system")
@contextmanager
def using_permissions(self, perms):
'''
Sets permissions for code being executed in a `with` block,
and restores them on exit.
:param perms: A dict containing one or more perms and their
@@ -1773,23 +1779,23 @@ class Marionette(object):
return self._send_message(
"findElements", body, key="value" if self.protocol == 1 else None)
def get_active_element(self):
el = self._send_message("getActiveElement", key="value")
return HTMLElement(self, el)
- def log(self, msg, level=None):
+ def log(self, msg, level="INFO"):
"""Stores a timestamped log message in the Marionette server
for later retrieval.
:param msg: String with message to log.
- :param level: String with log level (e.g. "INFO" or "DEBUG"). If None,
- defaults to "INFO".
+ :param level: String with log level (e.g. "INFO" or "DEBUG").
+ Defaults to "INFO".
"""
body = {"value": msg, "level": level}
self._send_message("log", body)
def get_logs(self):
"""Returns the list of logged messages.
Each log message is an array with three string elements: the level,
deleted file mode 100644
--- a/testing/marionette/common.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/* 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/. */
-
-// This file contains common code that is shared between
-// driver.jj and listener.js.
-
-/**
- * Creates an error message for a JavaScript exception thrown during
- * execute_(async_)script.
- *
- * This will generate a [msg, trace] pair like:
- *
- * ['ReferenceError: foo is not defined',
- * 'execute_script @test_foo.py, line 10
- * inline javascript, line 2
- * src: "return foo;"']
- *
- * @param error An Error object passed to a catch() clause.
- fnName The name of the function to use in the stack trace message
- (e.g., 'execute_script').
- pythonFile The filename of the test file containing the Marionette
- command that caused this exception to occur.
- pythonLine The line number of the above test file.
- script The JS script being executed in text form.
- */
-this.createStackMessage = function createStackMessage(error, fnName, pythonFile,
- pythonLine, script) {
- let python_stack = fnName + " @" + pythonFile;
- if (pythonLine !== null) {
- python_stack += ", line " + pythonLine;
- }
- let trace, msg;
- if (typeof(error) == "object" && 'name' in error && 'stack' in error) {
- let stack = error.stack.split("\n");
- let match = stack[0].match(/:(\d+):\d+$/);
- let line = match ? parseInt(match[1]) : 0;
- msg = error.name + ('message' in error ? ": " + error.message : "");
- trace = python_stack +
- "\ninline javascript, line " + line +
- "\nsrc: \"" + script.split("\n")[line] + "\"";
- }
- else {
- trace = python_stack;
- msg = error + "";
- }
- return [msg, trace];
-}
-
-this.MarionetteLogObj = function MarionetteLogObj() {
- this.logs = [];
-}
-MarionetteLogObj.prototype = {
- /**
- * Log message. Accepts user defined log-level.
- * @param msg String
- * The message to be logged
- * @param level String
- * The logging level to be used
- */
- log: function ML_log(msg, level) {
- let lev = level ? level : "INFO";
- this.logs.push( [lev, msg, (new Date()).toString()]);
- },
-
- /**
- * Add a list of logs to its list
- * @param msgs Object
- * Takes a list of strings
- */
- addLogs: function ML_addLogs(msgs) {
- for (let i = 0; i < msgs.length; i++) {
- this.logs.push(msgs[i]);
- }
- },
-
- /**
- * Return all logged messages.
- */
- getLogs: function ML_getLogs() {
- let logs = this.logs;
- this.clearLogs();
- return logs;
- },
-
- /**
- * Clears the logs
- */
- clearLogs: function ML_clearLogs() {
- this.logs = [];
- },
-}
--- a/testing/marionette/dispatcher.js
+++ b/testing/marionette/dispatcher.js
@@ -24,35 +24,35 @@ const logger = Log.repository.getLogger(
/**
* Manages a Marionette connection, and dispatches packets received to
* their correct destinations.
*
* @param {number} connId
* Unique identifier of the connection this dispatcher should handle.
* @param {DebuggerTransport} transport
* Debugger transport connection to the client.
- * @param {function(Emulator): GeckoDriver} driverFactory
- * A factory function that takes an Emulator as argument and produces
+ * @param {function(EmulatorService): GeckoDriver} driverFactory
+ * A factory function that takes an EmulatorService and produces
* a GeckoDriver.
*/
this.Dispatcher = function(connId, transport, driverFactory) {
this.connId = connId;
this.conn = transport;
// transport hooks are Dispatcher#onPacket
// and Dispatcher#onClosed
this.conn.hooks = this;
// callback for when connection is closed
this.onclose = null;
// last received/sent message ID
this.lastId = 0;
- this.emulator = new Emulator(this.sendEmulator.bind(this));
+ this.emulator = new emulator.EmulatorService(this.sendEmulator.bind(this));
this.driver = driverFactory(this.emulator);
// lookup of commands sent by server to client by message ID
this.commands_ = new Map();
};
/**
* Debugger transport callback that cleans up
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -7,39 +7,35 @@
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-var {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-this.DevToolsUtils = devtools.require("devtools/shared/DevToolsUtils");
-
XPCOMUtils.defineLazyServiceGetter(
this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
Cu.import("chrome://marionette/content/action.js");
Cu.import("chrome://marionette/content/atom.js");
Cu.import("chrome://marionette/content/element.js");
+Cu.import("chrome://marionette/content/emulator.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/evaluate.js");
Cu.import("chrome://marionette/content/event.js");
Cu.import("chrome://marionette/content/frame.js");
Cu.import("chrome://marionette/content/interaction.js");
+Cu.import("chrome://marionette/content/logging.js");
Cu.import("chrome://marionette/content/modal.js");
Cu.import("chrome://marionette/content/proxy.js");
Cu.import("chrome://marionette/content/simpletest.js");
-loader.loadSubScript("chrome://marionette/content/common.js");
-
this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];
var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
const BROWSER_STARTUP_FINISHED = "browser-delayed-startup-finished";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const CLICK_TO_START_PREF = "marionette.debugging.clicktostart";
const CONTENT_LISTENER_PREF = "marionette.contentListener";
@@ -86,18 +82,19 @@ this.Context.fromString = function(s) {
* object.
*
* @param {string} appName
* Description of the product, for example "B2G" or "Firefox".
* @param {string} device
* Device this driver should assume.
* @param {function()} stopSignal
* Signal to stop the Marionette server.
- * @param {Emulator=} emulator
- * Reference to the emulator connection, if running on an emulator.
+ * @param {EmulatorService=} emulator
+ * Interface that allows instructing the emulator connected to the
+ * client to run commands and perform shell invocations.
*/
this.GeckoDriver = function(appName, device, stopSignal, emulator) {
this.appName = appName;
this.stopSignal_ = stopSignal;
this.emulator = emulator;
// TODO(ato): hack
this.emulator.sendToListener = this.sendAsync.bind(this);
@@ -107,29 +104,27 @@ this.GeckoDriver = function(appName, dev
// points to current browser
this.curBrowser = null;
this.context = Context.CONTENT;
this.scriptTimeout = null;
this.searchTimeout = null;
this.pageTimeout = null;
this.timer = null;
this.inactivityTimer = null;
- // called by simpletest methods
- this.heartbeatCallback = function() {};
- this.marionetteLog = new MarionetteLogObj();
+ this.marionetteLog = new logging.ContentLogger();
// topmost chrome frame
this.mainFrame = null;
// chrome iframe that currently has focus
this.curFrame = null;
this.mainContentFrameId = null;
this.importedScripts = new evaluate.ScriptStorageService([Context.CHROME, Context.CONTENT]);
this.currentFrameElement = null;
this.testName = null;
this.mozBrowserClose = null;
- this.sandboxes = {};
+ this.sandboxes = new Sandboxes(() => this.getCurrentWindow());
// frame ID of the current remote frame, used for mozbrowserclose events
this.oopFrameId = null;
this.observing = null;
this._browserIds = new WeakMap();
this.actions = new action.Chain();
this.sessionCapabilities = {
// mandated capabilities
@@ -217,20 +212,20 @@ GeckoDriver.prototype.sendAsync = functi
// this can be removed.
if (cmdId) {
msg.command_id = cmdId;
}
if (curRemoteFrame === null) {
this.curBrowser.executeWhenReady(() => {
if (this.curBrowser.curFrameId) {
- this.mm.broadcastAsyncMessage(name + this.curBrowser.curFrameId, msg);
- }
- else {
- throw new WebDriverError("Can not send call to listener as it doesnt exist");
+ this.mm.broadcastAsyncMessage(name + this.curBrowser.curFrameId, msg);
+ } else {
+ throw new NoSuchFrameError(
+ "No such content frame; perhaps the listener was not registered?");
}
});
} else {
let remoteFrameId = curRemoteFrame.targetFrameId;
try {
this.mm.sendAsyncMessage(name + remoteFrameId, msg);
} catch (e) {
switch(e.result) {
@@ -249,17 +244,17 @@ GeckoDriver.prototype.sendAsync = functi
*
* @return {nsIDOMWindow}
*/
GeckoDriver.prototype.getCurrentWindow = function() {
let typ = null;
if (this.curFrame === null) {
if (this.curBrowser === null) {
if (this.context == Context.CONTENT) {
- typ = 'navigator:browser';
+ typ = "navigator:browser";
}
return Services.wm.getMostRecentWindow(typ);
} else {
return this.curBrowser.window;
}
} else {
return this.curFrame;
}
@@ -685,22 +680,25 @@ GeckoDriver.prototype.setUpProxy = funct
* Log message. Accepts user defined log-level.
*
* @param {string} value
* Log message.
* @param {string} level
* Arbitrary log level.
*/
GeckoDriver.prototype.log = function(cmd, resp) {
- this.marionetteLog.log(cmd.parameters.value, cmd.parameters.level);
+ // if level is null, we want to use ContentLogger#send's default
+ this.marionetteLog.log(
+ cmd.parameters.value,
+ cmd.parameters.level || undefined);
};
/** Return all logged messages. */
GeckoDriver.prototype.getLogs = function(cmd, resp) {
- resp.body = this.marionetteLog.getLogs();
+ resp.body = this.marionetteLog.get();
};
/**
* Sets the context of the subsequent commands to be either "chrome" or
* "content".
*
* @param {string} value
* Name of the context to be switched to. Must be one of "chrome" or
@@ -716,209 +714,221 @@ GeckoDriver.prototype.setContext = funct
};
/** Gets the context of the server, either "chrome" or "content". */
GeckoDriver.prototype.getContext = function(cmd, resp) {
resp.body.value = this.context.toString();
};
/**
- * Returns a chrome sandbox that can be used by the execute and
- * executeWithCallback functions.
+ * Executes a JavaScript function in the context of the current browsing
+ * context, if in content space, or in chrome space otherwise, and returns
+ * the return value of the function.
+ *
+ * It is important to note that if the {@code sandboxName} parameter
+ * is left undefined, the script will be evaluated in a mutable sandbox,
+ * causing any change it makes on the global state of the document to have
+ * lasting side-effects.
*
- * @param {nsIDOMWindow} win
- * Window in which we will execute code.
- * @param {Marionette} mn
- * Marionette test instance.
- * @param {string} sandboxName
- * The name for the sandbox. If 'system', create the sandbox
- * with elevated privileges.
+ * @param {string} script
+ * Script to evaluate as a function body.
+ * @param {Array.<(string|boolean|number|object|WebElement)>} args
+ * Arguments exposed to the script in {@code arguments}. The array
+ * items must be serialisable to the WebDriver protocol.
+ * @param {number} scriptTimeout
+ * Duration in milliseconds of when to interrupt and abort the
+ * script evaluation.
+ * @param {string=} sandbox
+ * Name of the sandbox to evaluate the script in. The sandbox is
+ * cached for later re-use on the same Window object if
+ * {@code newSandbox} is false. If he parameter is undefined,
+ * the script is evaluated in a mutable sandbox. If the parameter
+ * is "system", it will be evaluted in a sandbox with elevated system
+ * privileges, equivalent to chrome space.
+ * @param {boolean=} newSandbox
+ * Forces the script to be evaluated in a fresh sandbox. Note that if
+ * it is undefined, the script will normally be evaluted in a fresh
+ * sandbox.
+ * @param {string=} filename
+ * Filename of the client's program where this script is evaluated.
+ * @param {number=} line
+ * Line in the client's program where this script is evaluated.
+ * @param {boolean=} debug_script
+ * Attach an {@code onerror} event handler on the Window object.
+ * It does not differentiate content errors from chrome errors.
+ * @param {boolean=} directInject
+ * Evaluate the script without wrapping it in a function.
*
- * @return {nsIXPCComponents_utils_Sandbox}
- * Returns the sandbox.
+ * @return {(string|boolean|number|object|WebElement)}
+ * Return value from the script, or null which signifies either the
+ * JavaScript notion of null or undefined.
+ *
+ * @throws ScriptTimeoutError
+ * If the script was interrupted due to reaching the {@code
+ * scriptTimeout} or default timeout.
+ * @throws JavaScriptError
+ * If an Error was thrown whilst evaluating the script.
*/
-GeckoDriver.prototype.createExecuteSandbox = function(win, mn, sandboxName) {
- let principal = win;
- if (sandboxName == 'system') {
- principal = Cc["@mozilla.org/systemprincipal;1"].
- createInstance(Ci.nsIPrincipal);
- }
- let sb = new Cu.Sandbox(principal,
- {sandboxPrototype: win, wantXrays: false, sandboxName: ""});
- sb.global = sb;
- sb.proto = win;
+GeckoDriver.prototype.executeScript = function*(cmd, resp) {
+ let {script, args, scriptTimeout} = cmd.parameters;
+ scriptTimeout = scriptTimeout || this.scriptTimeout;
- mn.exports.forEach(function(fn) {
- if (typeof mn[fn] === 'function') {
- sb[fn] = mn[fn].bind(mn);
- } else {
- sb[fn] = mn[fn];
- }
- });
+ let opts = {
+ sandboxName: cmd.parameters.sandbox,
+ newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
+ cmd.parameters.newSandbox,
+ filename: cmd.parameters.filename,
+ line: cmd.parameters.line,
+ debug: cmd.parameters.debug_script,
+ };
- sb.isSystemMessageListenerReady = () => systemMessageListenerReady;
-
- this.sandboxes[sandboxName] = sb;
+ resp.body.value = yield this.execute_(script, args, scriptTimeout, opts);
};
/**
- * Apply arguments sent from the client to the current (possibly reused)
- * execution sandbox.
+ * Executes a JavaScript function in the context of the current browsing
+ * context, if in content space, or in chrome space otherwise, and returns
+ * the object passed to the callback.
+ *
+ * The callback is always the last argument to the {@code arguments}
+ * list passed to the function scope of the script. It can be retrieved
+ * as such:
+ *
+ * let callback = arguments[arguments.length - 1];
+ * callback("foo");
+ * // "foo" is returned
+ *
+ * It is important to note that if the {@code sandboxName} parameter
+ * is left undefined, the script will be evaluated in a mutable sandbox,
+ * causing any change it makes on the global state of the document to have
+ * lasting side-effects.
+ *
+ * @param {string} script
+ * Script to evaluate as a function body.
+ * @param {Array.<(string|boolean|number|object|WebElement)>} args
+ * Arguments exposed to the script in {@code arguments}. The array
+ * items must be serialisable to the WebDriver protocol.
+ * @param {number} scriptTimeout
+ * Duration in milliseconds of when to interrupt and abort the
+ * script evaluation.
+ * @param {string=} sandbox
+ * Name of the sandbox to evaluate the script in. The sandbox is
+ * cached for later re-use on the same Window object if
+ * {@code newSandbox} is false. If he parameter is undefined,
+ * the script is evaluated in a mutable sandbox. If the parameter
+ * is "system", it will be evaluted in a sandbox with elevated system
+ * privileges, equivalent to chrome space.
+ * @param {boolean=} newSandbox
+ * Forces the script to be evaluated in a fresh sandbox. Note that if
+ * it is undefined, the script will normally be evaluted in a fresh
+ * sandbox.
+ * @param {string=} filename
+ * Filename of the client's program where this script is evaluated.
+ * @param {number=} line
+ * Line in the client's program where this script is evaluated.
+ * @param {boolean=} debug_script
+ * Attach an {@code onerror} event handler on the Window object.
+ * It does not differentiate content errors from chrome errors.
+ * @param {boolean=} directInject
+ * Evaluate the script without wrapping it in a function.
+ *
+ * @return {(string|boolean|number|object|WebElement)}
+ * Return value from the script, or null which signifies either the
+ * JavaScript notion of null or undefined.
+ *
+ * @throws ScriptTimeoutError
+ * If the script was interrupted due to reaching the {@code
+ * scriptTimeout} or default timeout.
+ * @throws JavaScriptError
+ * If an Error was thrown whilst evaluating the script.
*/
-GeckoDriver.prototype.applyArgumentsToSandbox = function(win, sb, args) {
- sb.__marionetteParams = this.curBrowser.elementManager.convertWrappedArguments(args,
- { frame: win });
- sb.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
+GeckoDriver.prototype.executeAsyncScript = function(cmd, resp) {
+ let {script, args, scriptTimeout} = cmd.parameters;
+ scriptTimeout = scriptTimeout || this.scriptTimeout;
+
+ let opts = {
+ sandboxName: cmd.parameters.sandbox,
+ newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
+ cmd.parameters.newSandbox,
+ filename: cmd.parameters.filename,
+ line: cmd.parameters.line,
+ debug: cmd.parameters.debug_script,
+ async: true,
+ };
+
+ resp.body.value = yield this.execute_(script, args, scriptTimeout, opts);
};
-/**
- * Executes a script in the given sandbox.
- *
- * @param {Response} resp
- * Response object given to the command calling this routine.
- * @param {nsIXPCComponents_utils_Sandbox} sandbox
- * Sandbox in which the script will run.
- * @param {string} script
- * Script to run.
- * @param {boolean} directInject
- * If true, then the script will be run as is, and not as a function
- * body (as you would do using the WebDriver spec).
- * @param {boolean} async
- * True if the script is asynchronous.
- * @param {number} timeout
- * When to interrupt script in milliseconds.
- * @param {string} filename
- * Optional. URI or name of the file we are executing.
- * (Used to improve stack trace readability)
- */
-GeckoDriver.prototype.executeScriptInSandbox = function(
- resp,
- sandbox,
- script,
- directInject,
- async,
- timeout,
- filename) {
- if (directInject && async && (timeout === null || timeout === 0)) {
- throw new TimeoutError("Please set a timeout");
- }
+GeckoDriver.prototype.execute_ = function(script, args, timeout, opts = {}) {
+ switch (this.context) {
+ case Context.CONTENT:
+ // evaluate in content with lasting side-effects
+ if (!opts.sandboxName) {
+ return this.listener.execute(script, args, timeout, opts);
+
+ // evaluate in content with sandbox
+ } else {
+ return this.listener.executeInSandbox(script, args, timeout, opts);
+ }
- script = this.importedScripts.for(Context.CHROME).concat(script);
- let res = Cu.evalInSandbox(script, sandbox, "1.8", filename ? filename : "dummy file", 0);
+ case Context.CHROME:
+ let sb = this.sandboxes.get(opts.sandboxName, opts.newSandbox);
+ if (opts.sandboxName) {
+ sb = sandbox.augment(sb, new logging.Adapter(this.marionetteLog));
+ sb = sandbox.augment(sb, {global: sb});
+ sb = sandbox.augment(sb, new emulator.Adapter(this.emulator));
+ }
- if (directInject && !async &&
- (typeof res == "undefined" || typeof res.passed == "undefined")) {
- throw new WebDriverError("finish() not called");
- }
-
- if (!async) {
- // It's fine to pass on and modify resp here because
- // executeScriptInSandbox is the last function to be called
- // in execute and executeWithCallback respectively.
- resp.body.value = this.curBrowser.elementManager.wrapValue(res);
+ opts.timeout = timeout;
+ script = this.importedScripts.for(Context.CHROME).concat(script);
+ let wargs = this.curBrowser.elementManager.convertWrappedArguments(args, {frame: sb.window});
+ let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
+ return evaluatePromise.then(res => this.curBrowser.elementManager.wrapValue(res));
}
};
/**
- * Execute the given script either as a function body or directly (for
- * mochitest-like JS Marionette tests).
+ * Execute pure JavaScript. Used to execute simpletest harness tests,
+ * which are like mochitests only injected using Marionette.
*
- * If directInject is ture, it will run directly and not as a function
- * body.
+ * Scripts are expected to call the {@code finish} global when done.
*/
-GeckoDriver.prototype.execute = function*(cmd, resp, directInject) {
- let {inactivityTimeout,
- scriptTimeout,
- script,
- newSandbox,
- args,
- filename,
- line} = cmd.parameters;
- let sandboxName = cmd.parameters.sandbox || 'default';
+GeckoDriver.prototype.executeJSScript = function(cmd, resp) {
+ let {script, args, scriptTimeout} = cmd.parameters;
+ scriptTimeout = scriptTimeout || this.scriptTimeout;
- if (!scriptTimeout) {
- scriptTimeout = this.scriptTimeout;
- }
- if (typeof newSandbox == "undefined") {
- newSandbox = true;
- }
-
- if (this.context == Context.CONTENT) {
- resp.body.value = yield this.listener.executeScript({
- script: script,
- args: args,
- newSandbox: newSandbox,
- timeout: scriptTimeout,
- filename: filename,
- line: line,
- sandboxName: sandboxName
- });
- return;
- }
+ let opts = {
+ filename: cmd.parameters.filename,
+ line: cmd.parameters.line,
+ async: cmd.parameters.async,
+ };
- // handle the inactivity timeout
- let that = this;
- if (inactivityTimeout) {
- let setTimer = function() {
- that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- if (that.inactivityTimer !== null) {
- that.inactivityTimer.initWithCallback(function() {
- throw new ScriptTimeoutError("timed out due to inactivity");
- }, inactivityTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
- }
- };
- setTimer();
- this.heartbeatCallback = function() {
- that.inactivityTimer.cancel();
- setTimer();
- };
- }
+ switch (this.context) {
+ case Context.CHROME:
+ let win = this.getCurrentWindow();
+ let wargs = this.curBrowser.elementManager.convertWrappedArguments(args, {frame: win});
+ let harness = new simpletest.Harness(
+ win,
+ Context.CHROME,
+ this.marionetteLog,
+ scriptTimeout,
+ function() {},
+ this.testName);
- let win = this.getCurrentWindow();
- if (newSandbox ||
- !(sandboxName in this.sandboxes) ||
- (this.sandboxes[sandboxName].proto != win)) {
- let marionette = new Marionette(
- win,
- "chrome",
- this.marionetteLog,
- scriptTimeout,
- this.heartbeatCallback,
- this.testName);
- this.createExecuteSandbox(
- win,
- marionette,
- sandboxName);
- if (!this.sandboxes[sandboxName]) {
- return;
- }
- }
- this.applyArgumentsToSandbox(win, this.sandboxes[sandboxName], args);
+ let sb = sandbox.createSimpleTest(win, harness);
+ // TODO(ato): Not sure this is needed:
+ sb = sandbox.augment(sb, new logging.Adapter(this.marionetteLog));
- try {
- this.sandboxes[sandboxName].finish = () => {
- if (this.inactivityTimer !== null) {
- this.inactivityTimer.cancel();
- }
- return this.sandboxes[sandboxName].generate_results();
- };
+ let res = yield evaluate.sandbox(sb, script, wargs, opts);
+ resp.body.value = this.curBrowser.elementManager.wrapValue(res);
+ break;
- if (!directInject) {
- script = "var func = function() { " + script + " }; func.apply(null, __marionetteParams);";
- }
- this.executeScriptInSandbox(
- resp,
- this.sandboxes[sandboxName],
- script,
- directInject,
- false /* async */,
- scriptTimeout,
- filename);
- } catch (e) {
- throw new JavaScriptError(e, "execute_script", filename, line, script);
+ case Context.CONTENT:
+ resp.body.value = yield this.listener.executeSimpleTest(script, args, scriptTimeout, opts);
+ break;
}
};
/**
* Set the timeout for asynchronous script execution.
*
* @param {number} ms
* Time in milliseconds.
@@ -927,227 +937,16 @@ GeckoDriver.prototype.setScriptTimeout =
let ms = parseInt(cmd.parameters.ms);
if (isNaN(ms)) {
throw new WebDriverError("Not a Number");
}
this.scriptTimeout = ms;
};
/**
- * Execute pure JavaScript. Used to execute mochitest-like Marionette
- * tests.
- */
-GeckoDriver.prototype.executeJSScript = function*(cmd, resp) {
- // TODO(ato): cmd.newSandbox doesn't ever exist?
- // All pure JS scripts will need to call
- // Marionette.finish() to complete the test
- if (typeof cmd.newSandbox == "undefined") {
- // If client does not send a value in newSandbox,
- // then they expect the same behaviour as WebDriver.
- cmd.newSandbox = true;
- }
-
- switch (this.context) {
- case Context.CHROME:
- if (cmd.parameters.async) {
- yield this.executeWithCallback(cmd, resp, cmd.parameters.async);
- } else {
- this.execute(cmd, resp, true /* async */);
- }
- break;
-
- case Context.CONTENT:
- resp.body.value = yield this.listener.executeJSScript({
- script: cmd.parameters.script,
- args: cmd.parameters.args,
- newSandbox: cmd.parameters.newSandbox,
- async: cmd.parameters.async,
- timeout: cmd.parameters.scriptTimeout ?
- cmd.parameters.scriptTimeout : this.scriptTimeout,
- inactivityTimeout: cmd.parameters.inactivityTimeout,
- filename: cmd.parameters.filename,
- line: cmd.parameters.line,
- sandboxName: cmd.parameters.sandbox || 'default',
- });
- break;
- }
-};
-
-/**
- * This function is used by executeAsync and executeJSScript to execute
- * a script in a sandbox.
- *
- * For executeJSScript, it will return a message only when the finish()
- * method is called.
- *
- * For executeAsync, it will return a response when
- * {@code marionetteScriptFinished} (equivalent to
- * {@code arguments[arguments.length-1]}) function is called,
- * or if it times out.
- *
- * If directInject is true, it will be run directly and not as a
- * function body.
- */
-GeckoDriver.prototype.executeWithCallback = function*(cmd, resp, directInject) {
- let {script,
- args,
- newSandbox,
- inactivityTimeout,
- scriptTimeout,
- filename,
- line} = cmd.parameters;
- let sandboxName = cmd.parameters.sandbox || "default";
-
- if (!scriptTimeout) {
- scriptTimeout = this.scriptTimeout;
- }
- if (typeof newSandbox == "undefined") {
- newSandbox = true;
- }
-
- if (this.context == Context.CONTENT) {
- resp.body.value = yield this.listener.executeAsyncScript({
- script: script,
- args: args,
- id: cmd.id,
- newSandbox: newSandbox,
- timeout: scriptTimeout,
- inactivityTimeout: inactivityTimeout,
- filename: filename,
- line: line,
- sandboxName: sandboxName,
- });
- return;
- }
-
- // handle the inactivity timeout
- let that = this;
- if (inactivityTimeout) {
- this.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- if (this.inactivityTimer !== null) {
- this.inactivityTimer.initWithCallback(function() {
- chromeAsyncReturnFunc(new ScriptTimeoutError("timed out due to inactivity"));
- }, inactivityTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
- }
- this.heartbeatCallback = function resetInactivityTimer() {
- that.inactivityTimer.cancel();
- that.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- if (that.inactivityTimer !== null) {
- that.inactivityTimer.initWithCallback(function() {
- chromeAsyncReturnFunc(new ScriptTimeoutError("timed out due to inactivity"));
- }, inactivityTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
- }
- };
- }
-
- let win = this.getCurrentWindow();
- let origOnError = win.onerror;
- that.timeout = scriptTimeout;
-
- let res = yield new Promise(function(resolve, reject) {
- let chromeAsyncReturnFunc = function(val) {
- if (cmd.id == that.sandboxes[sandboxName].command_id) {
- if (that.timer !== null) {
- that.timer.cancel();
- that.timer = null;
- }
-
- win.onerror = origOnError;
-
- if (error.isError(val)) {
- reject(val);
- } else {
- resolve(val);
- }
- }
-
- if (that.inactivityTimer !== null) {
- that.inactivityTimer.cancel();
- }
- };
-
- let chromeAsyncFinish = function() {
- let res = that.sandboxes[sandboxName].generate_results();
- chromeAsyncReturnFunc(res);
- };
-
- let chromeAsyncError = function(e, func, file, line, script) {
- let err = new JavaScriptError(e, func, file, line, script);
- chromeAsyncReturnFunc(err);
- };
-
- if (newSandbox || !(sandboxName in this.sandboxes)) {
- let marionette = new Marionette(
- win,
- "chrome",
- this.marionetteLog,
- scriptTimeout,
- this.heartbeatCallback,
- this.testName);
- this.createExecuteSandbox(win, marionette, sandboxName);
- }
- if (!this.sandboxes[sandboxName]) {
- return;
- }
-
- this.sandboxes[sandboxName].command_id = cmd.id;
- this.sandboxes[sandboxName].runEmulatorCmd =
- (cmd, cb) => this.emulator.command(cmd, cb, chromeAsyncError);
- this.sandboxes[sandboxName].runEmulatorShell =
- (args, cb) => this.emulator.shell(args, cb, chromeAsyncError);
-
- this.applyArgumentsToSandbox(win, this.sandboxes[sandboxName], args);
-
- // NB: win.onerror is not hooked by default due to the inability to
- // differentiate content exceptions from chrome exceptions. See bug
- // 1128760 for more details. A debug_script flag can be set to
- // reenable onerror hooking to help debug test scripts.
- if (cmd.parameters.debug_script) {
- win.onerror = function(msg, url, line) {
- let err = new JavaScriptError(`${msg} at: ${url} line: ${line}`);
- chromeAsyncReturnFunc(err);
- return true;
- };
- }
-
- try {
- this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- if (this.timer !== null) {
- this.timer.initWithCallback(function() {
- chromeAsyncReturnFunc(new ScriptTimeoutError("timed out"));
- }, that.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
- }
-
- this.sandboxes[sandboxName].returnFunc = chromeAsyncReturnFunc;
- this.sandboxes[sandboxName].finish = chromeAsyncFinish;
-
- if (!directInject) {
- script = "__marionetteParams.push(returnFunc);" +
- "var marionetteScriptFinished = returnFunc;" +
- "var __marionetteFunc = function() {" + script + "};" +
- "__marionetteFunc.apply(null, __marionetteParams);";
- }
-
- this.executeScriptInSandbox(
- resp,
- this.sandboxes[sandboxName],
- script,
- directInject,
- true /* async */,
- scriptTimeout,
- filename);
- } catch (e) {
- chromeAsyncError(e, "execute_async_script", filename, line, script);
- }
- }.bind(this));
-
- resp.body.value = that.curBrowser.elementManager.wrapValue(res) || null;
-};
-
-/**
* Navigate to given URL.
*
* Navigates the current browsing context to the given URL and waits for
* the document to load or the session's page timeout duration to elapse
* before returning.
*
* The command will return with a failure if there is an error loading
* the document or the URL is blocked. This can occur if it fails to
@@ -2442,17 +2241,17 @@ GeckoDriver.prototype.sessionTearDown =
this.sessionId = null;
if (this.observing !== null) {
for (let topic in this.observing) {
Services.obs.removeObserver(this.observing[topic], topic);
}
this.observing = null;
}
- this.sandboxes = {};
+ this.sandboxes.clear();
};
/**
* Processes the "deleteSession" request from the client by tearing down
* the session and responding "ok".
*/
GeckoDriver.prototype.deleteSession = function(cmd, resp) {
this.sessionTearDown();
@@ -2789,17 +2588,17 @@ GeckoDriver.prototype.receiveMessage = f
case "Marionette:log":
// log server-side messages
logger.info(message.json.message);
break;
case "Marionette:shareData":
// log messages from tests
if (message.json.log) {
- this.marionetteLog.addLogs(message.json.log);
+ this.marionetteLog.addAll(message.json.log);
}
break;
case "Marionette:switchToModalOrigin":
this.curBrowser.frameManager.switchToModalOrigin(message);
this.mm = this.curBrowser.frameManager
.currentRemoteFrame.messageManager.get();
break;
@@ -2897,23 +2696,23 @@ GeckoDriver.prototype.commands = {
"getMarionetteID": GeckoDriver.prototype.getMarionetteID,
"sayHello": GeckoDriver.prototype.sayHello,
"newSession": GeckoDriver.prototype.newSession,
"getSessionCapabilities": GeckoDriver.prototype.getSessionCapabilities,
"log": GeckoDriver.prototype.log,
"getLogs": GeckoDriver.prototype.getLogs,
"setContext": GeckoDriver.prototype.setContext,
"getContext": GeckoDriver.prototype.getContext,
- "executeScript": GeckoDriver.prototype.execute,
+ "executeScript": GeckoDriver.prototype.executeScript,
"setScriptTimeout": GeckoDriver.prototype.setScriptTimeout,
"timeouts": GeckoDriver.prototype.timeouts,
"singleTap": GeckoDriver.prototype.singleTap,
"actionChain": GeckoDriver.prototype.actionChain,
"multiAction": GeckoDriver.prototype.multiAction,
- "executeAsyncScript": GeckoDriver.prototype.executeWithCallback,
+ "executeAsyncScript": GeckoDriver.prototype.executeAsyncScript,
"executeJSScript": GeckoDriver.prototype.executeJSScript,
"setSearchTimeout": GeckoDriver.prototype.setSearchTimeout,
"findElement": GeckoDriver.prototype.findElement,
"findElements": GeckoDriver.prototype.findElements,
"clickElement": GeckoDriver.prototype.clickElement,
"getElementAttribute": GeckoDriver.prototype.getElementAttribute,
"getElementText": GeckoDriver.prototype.getElementText,
"getElementTagName": GeckoDriver.prototype.getElementTagName,
--- a/testing/marionette/emulator.js
+++ b/testing/marionette/emulator.js
@@ -1,111 +1,166 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("chrome://marionette/content/error.js");
+
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
const logger = Log.repository.getLogger("Marionette");
-this.EXPORTED_SYMBOLS = ["Emulator"];
+this.EXPORTED_SYMBOLS = ["emulator"];
+
+this.emulator = {};
/**
- * Represents the connection between Marionette and the emulator it's
- * running on.
- *
- * When injected scripts call the JS routines {@code runEmulatorCmd} or
- * {@code runEmulatorShell}, the second argument to those is a callback
- * which is stored in cbs. They are later retreived by their unique ID
- * using popCallback.
- *
- * @param {function(Object)} sendToEmulatorFn
- * Callback function that sends a message to the emulator.
- * @param {function(Object)} sendToEmulatorFn
- * Callback function that sends a message asynchronously to the
- * current listener.
+ * Provides a service for instructing the emulator attached to the
+ * currently connected client to perform shell- and command instructions.
*/
-this.Emulator = function(sendToEmulatorFn) {
- this.sendToEmulator = sendToEmulatorFn;
+emulator.EmulatorService = class {
+ /*
+ * @param {function(Object)} sendToEmulatorFn
+ * Callback function that sends a message to the emulator.
+ */
+ constructor(sendToEmulatorFn) {
+ this.sendToEmulator = sendToEmulatorFn;
+ }
+
+ /**
+ * Instruct the client to run an Android emulator command.
+ *
+ * @param {string} cmd
+ * The command to run.
+ * @param {function(?)} resCb
+ * Callback on a result response from the emulator.
+ * @param {function(?)} errCb
+ * Callback on an error in running the command.
+ */
+ command(cmd, resCb, errCb) {
+ if (arguments.length < 1) {
+ throw new ValueError("Not enough arguments");
+ }
+ this.sendToEmulator(
+ "runEmulatorCmd", {emulator_cmd: cmd}, resCb, errCb);
+ }
+
+ /**
+ * Instruct the client to execute Android emulator shell arguments.
+ *
+ * @param {Array.<string>} args
+ * The shell instruction for the emulator to execute.
+ * @param {function(?)} resCb
+ * Callback on a result response from the emulator.
+ * @param {function(?)} errCb
+ * Callback on an error in executing the shell arguments.
+ */
+ shell(args, resCb, errCb) {
+ if (arguments.length < 1) {
+ throw new ValueError("Not enough arguments");
+ }
+ this.sendToEmulator(
+ "runEmulatorShell", {emulator_shell: args}, resCb, errCb);
+ }
+
+ processMessage(msg) {
+ let resCb = this.resultCallback(msg.json.id);
+ let errCb = this.errorCallback(msg.json.id);
+
+ switch (msg.name) {
+ case "Marionette:runEmulatorCmd":
+ this.command(msg.json.arguments, resCb, errCb);
+ break;
+
+ case "Marionette:runEmulatorShell":
+ this.shell(msg.json.arguments, resCb, errCb);
+ break;
+ }
+ }
+
+ resultCallback(uuid) {
+ return res => this.sendResult({value: res, id: uuid});
+ }
+
+ errorCallback(uuid) {
+ return err => this.sendResult({error: err, id: uuid});
+ }
+
+ sendResult(msg) {
+ // sendToListener set explicitly in GeckoDriver's ctor
+ this.sendToListener("listenerResponse", msg);
+ }
+
+ /** Receives IPC messages from the listener. */
+ // TODO(ato): The idea of services in chrome space
+ // can be generalised at some later time.
+ receiveMessage(msg) {
+ let uuid = msg.json.id;
+ try {
+ this.processMessage(msg);
+ } catch (e) {
+ this.sendResult({error: `${e.name}: ${e.message}`, id: uuid});
+ }
+ }
};
-/**
- * Instruct the client to run an Android emulator command.
- *
- * @param {string} cmd
- * The command to run.
- * @param {function(?)} resCb
- * Callback on a result response from the emulator.
- * @param {function(?)} errCb
- * Callback on an error in running the command.
- */
-Emulator.prototype.command = function(cmd, resCb, errCb) {
- assertDefined(cmd, "runEmulatorCmd");
- this.sendToEmulator(
- "runEmulatorCmd", {emulator_cmd: cmd}, resCb, errCb);
+emulator.EmulatorService.prototype.QueryInterface =
+ XPCOMUtils.generateQI([
+ Ci.nsIMessageListener,
+ Ci.nsISupportsWeakReference
+ ]);
+
+emulator.EmulatorServiceClient = class {
+ constructor(chromeProxy) {
+ this.chrome = chromeProxy;
+ }
+
+ *command(cmd, cb) {
+ let res = yield this.chrome.runEmulatorCmd(cmd);
+ if (cb) {
+ cb(res);
+ }
+ }
+
+ *shell(args, cb) {
+ let res = yield this.chrome.runEmulatorShell(args);
+ if (cb) {
+ cb(res);
+ }
+ }
};
/**
- * Instruct the client to execute Android emulator shell arguments.
- *
- * @param {Array.<string>} args
- * The shell instruction for the emulator to execute.
- * @param {function(?)} resCb
- * Callback on a result response from the emulator.
- * @param {function(?)} errCb
- * Callback on an error in executing the shell arguments.
+ * Adapts EmulatorService for use with sandboxes that scripts are
+ * evaluated in. Is consumed by sandbox.augment.
*/
-Emulator.prototype.shell = function(args, resCb, errCb) {
- assertDefined(args, "runEmulatorShell");
- this.sendToEmulator(
- "runEmulatorShell", {emulator_shell: args}, resCb, errCb);
-};
+emulator.Adapter = class {
+ constructor(emulator) {
+ this.emulator = emulator;
+ }
+
+ get exports() {
+ return new Map([
+ ["runEmulatorCmd", this.runEmulatorCmd.bind(this)],
+ ["runEmulatorShell", this.runEmulatorShell.bind(this)],
+ ]);
+ }
-Emulator.prototype.processMessage = function(msg) {
- let resCb = this.resultCallback(msg.json.id);
- let errCb = this.errorCallback(msg.json.id);
+ runEmulatorCmd(cmd, cb) {
+ this.yield(this.emulator.command(cmd, cb));
+ }
- switch (msg.name) {
- case "Marionette:runEmulatorCmd":
- this.command(msg.json.command, resCb, errCb);
- break;
+ runEmulatorShell(args, cb) {
+ this.yield(this.emulator.shell(args, cb));
+ }
- case "Marionette:runEmulatorShell":
- this.shell(msg.json.arguments, resCb, errCb);
- break;
+ yield(promise) {
+ Task.spawn(function() {
+ yield promise;
+ });
}
};
-
-Emulator.prototype.resultCallback = function(msgId) {
- return res => this.sendResult({result: res, id: msgId});
-};
-
-Emulator.prototype.errorCallback = function(msgId) {
- return err => this.sendResult({error: err, id: msgId});
-};
-
-Emulator.prototype.sendResult = function(msg) {
- // sendToListener set explicitly in GeckoDriver's ctor
- this.sendToListener("emulatorCmdResult", msg);
-};
-
-/** Receives IPC messages from the listener. */
-Emulator.prototype.receiveMessage = function(msg) {
- try {
- this.processMessage(msg);
- } catch (e) {
- this.sendResult({error: `${e.name}: ${e.message}`, id: msg.json.id});
- }
-};
-
-Emulator.prototype.QueryInterface = XPCOMUtils.generateQI(
- [Ci.nsIMessageListener, Ci.nsISupportsWeakReference]);
-
-function assertDefined(arg, action) {
- if (typeof arg == "undefined") {
- throw new TypeError("Not enough arguments to " + action);
- }
-}
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -80,16 +80,27 @@ error.isWebDriverError = function(obj) {
error.wrap = function(err) {
if (error.isWebDriverError(err)) {
return err;
}
return new WebDriverError(`${err.name}: ${err.message}`, err.stack);
};
/**
+ * Wraps an Error as a WebDriverError type. If the given error is already
+ * in the WebDriverError prototype chain, this function acts as a no-op.
+ */
+error.wrap = function(err) {
+ if (error.isWebDriverError(err)) {
+ return err;
+ }
+ return new WebDriverError(err.message, err.stacktrace);
+};
+
+/**
* Unhandled error reporter. Dumps the error and its stacktrace to console,
* and reports error to the Browser Console.
*/
error.report = function(err) {
let msg = `Marionette threw an error: ${error.stringify(err)}`;
dump(msg + "\n");
if (Cu.reportError) {
Cu.reportError(msg);
@@ -161,16 +172,17 @@ error.fromJson = function(json) {
* WebDriverError is the prototypal parent of all WebDriver errors.
* It should not be used directly, as it does not correspond to a real
* error in the specification.
*/
this.WebDriverError = function(msg, stack = undefined) {
Error.call(this, msg);
this.name = "WebDriverError";
this.message = msg;
+ this.stack = stack;
this.status = "webdriver error";
this.stack = stack;
};
WebDriverError.prototype = Object.create(Error.prototype);
this.ElementNotAccessibleError = function(msg) {
WebDriverError.call(this, msg);
this.name = "ElementNotAccessibleError";
--- a/testing/marionette/evaluate.js
+++ b/testing/marionette/evaluate.js
@@ -2,25 +2,319 @@
* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("chrome://marionette/content/error.js");
+
const logger = Log.repository.getLogger("Marionette");
-this.EXPORTED_SYMBOLS = ["evaluate"];
+this.EXPORTED_SYMBOLS = ["evaluate", "sandbox", "Sandboxes"];
+
+const ARGUMENTS = "__webDriverArguments";
+const CALLBACK = "__webDriverCallback";
+const COMPLETE = "__webDriverComplete";
+const DEFAULT_TIMEOUT = 10000; // ms
+const FINISH = "finish";
+const MARIONETTE_SCRIPT_FINISHED = "marionetteScriptFinished";
+const ELEMENT_KEY = "element";
+const W3C_ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";
this.evaluate = {};
/**
+ * Evaluate a script in given sandbox.
+ *
+ * If the option {@code directInject} is not specified, the script will
+ * be executed as a function with the {@code args} argument applied.
+ *
+ * The arguments provided by the {@code args} argument are exposed through
+ * the {@code arguments} object available in the script context, and if
+ * the script is executed asynchronously with the {@code async}
+ * option, an additional last argument that is synonymous to the
+ * {@code marionetteScriptFinished} global is appended, and can be
+ * accessed through {@code arguments[arguments.length - 1]}.
+ *
+ * The {@code timeout} option specifies the duration for how long the
+ * script should be allowed to run before it is interrupted and aborted.
+ * An interrupted script will cause a ScriptTimeoutError to occur.
+ *
+ * The {@code async} option indicates that the script will not return
+ * until the {@code marionetteScriptFinished} global callback is invoked,
+ * which is analogous to the last argument of the {@code arguments}
+ * object.
+ *
+ * The option {@code directInject} causes the script to be evaluated
+ * without being wrapped in a function and the provided arguments will
+ * be disregarded. This will cause such things as root scope return
+ * statements to throw errors because they are not used inside a function.
+ *
+ * The {@code filename} option is used in error messages to provide
+ * information on the origin script file in the local end.
+ *
+ * The {@code line} option is used in error messages, along with
+ * {@code filename}, to provide the line number in the origin script
+ * file on the local end.
+ *
+ * @param {nsISandbox) sb
+ * The sandbox the script will be evaluted in.
+ * @param {string} script
+ * The script to evaluate.
+ * @param {Array.<?>=} args
+ * A sequence of arguments to call the script with.
+ * @param {Object.<string, ?>=} opts
+ * Dictionary of options:
+ *
+ * async (boolean)
+ * Indicates if the script should return immediately or wait
+ * for the callback be invoked before returning.
+ * debug (boolean)
+ * Attaches an {@code onerror} event listener.
+ * directInject (boolean)
+ * Evaluates the script without wrapping it in a function.
+ * filename (string)
+ * File location of the program in the client.
+ * line (number)
+ * Line number of the program in the client.
+ * sandboxName (string)
+ * Name of the sandbox. Elevated system privileges, equivalent
+ * to chrome space, will be given if it is "system".
+ * timeout (boolean)
+ * Duration in milliseconds before interrupting the script.
+ *
+ * @return {Promise}
+ * A promise that when resolved will give you the return value from
+ * the script. Note that the return value requires serialisation before
+ * it can be sent to the client.
+ *
+ * @throws JavaScriptError
+ * If an Error was thrown whilst evaluating the script.
+ * @throws ScriptTimeoutError
+ * If the script was interrupted due to script timeout.
+ */
+evaluate.sandbox = function(sb, script, args = [], opts = {}) {
+ let timeoutId, timeoutHandler, unloadHandler;
+
+ let promise = new Promise((resolve, reject) => {
+ sb[COMPLETE] = resolve;
+ timeoutHandler = () => reject(new ScriptTimeoutError("Timed out"));
+ unloadHandler = () => reject(
+ new JavaScriptError("Document was unloaded during execution"));
+
+ // wrap in function
+ if (!opts.directInject) {
+ sb[CALLBACK] = sb[COMPLETE];
+ sb[ARGUMENTS] = Cu.cloneInto(args, sb, {wrapReflectors: true});
+
+ script = `${ARGUMENTS}.push(${CALLBACK});` +
+ `(function() { ${script} }).apply(null, ${ARGUMENTS})`;
+
+ // marionetteScriptFinished is not WebDriver conformant,
+ // hence it is only exposed to immutable sandboxes
+ if (opts.sandboxName) {
+ sb[MARIONETTE_SCRIPT_FINISHED] = sb[CALLBACK];
+ }
+ }
+
+ // onerror is not hooked on by default because of the inability to
+ // differentiate content errors from chrome errors.
+ //
+ // see bug 1128760 for more details
+ if (opts.debug) {
+ sb.window.onerror = (msg, url, line) => {
+ let err = new JavaScriptError(`${msg} at: ${url} line: ${line}`);
+ reject(err);
+ };
+ }
+
+ // timeout and unload handlers
+ timeoutId = sb.window.setTimeout(
+ timeoutHandler, opts.timeout || DEFAULT_TIMEOUT);
+ sb.window.addEventListener("unload", unloadHandler);
+
+ let res;
+ try {
+ res = Cu.evalInSandbox(
+ script, sb, "1.8", opts.filename || "dummy file", 0);
+ } catch (e) {
+ let err = new JavaScriptError(
+ e,
+ "execute_script",
+ opts.filename,
+ opts.line,
+ script);
+ reject(err);
+ }
+
+ if (!opts.async) {
+ resolve(res);
+ }
+ });
+
+ return promise.then(res => {
+ sb.window.clearTimeout(timeoutId);
+ sb.window.removeEventListener("unload", unloadHandler);
+ return res;
+ });
+};
+
+this.sandbox = {};
+
+/**
+ * Augment given sandbox by an adapter that has an {@code exports}
+ * map property, or a normal map, of function names and function
+ * references.
+ *
+ * @param {Sandbox} sb
+ * The sandbox to augment.
+ * @param {Object} adapter
+ * Object that holds an {@code exports} property, or a map, of
+ * function names and function references.
+ *
+ * @return {Sandbox}
+ * The augmented sandbox.
+ */
+sandbox.augment = function(sb, adapter) {
+ function* entries(obj) {
+ for (let key of Object.keys(obj)) {
+ yield [key, obj[key]];
+ }
+ }
+
+ let funcs = adapter.exports || entries(adapter);
+ for (let [name, func] of funcs) {
+ sb[name] = func;
+ }
+
+ return sb;
+};
+
+/**
+ * Creates a sandbox.
+ *
+ * @param {Window} window
+ * The DOM Window object.
+ * @param {nsIPrincipal=} principal
+ * An optional, custom principal to prefer over the Window. Useful if
+ * you need elevated security permissions.
+ *
+ * @return {Sandbox}
+ * The created sandbox.
+ */
+sandbox.create = function(window, principal = null, opts = {}) {
+ let p = principal || window;
+ opts = Object.assign({
+ sameZoneAs: window,
+ sandboxPrototype: window,
+ wantComponents: true,
+ wantXrays: true,
+ }, opts);
+ return new Cu.Sandbox(p, opts);
+};
+
+/**
+ * Creates a mutable sandbox, where changes to the global scope
+ * will have lasting side-effects.
+ *
+ * @param {Window} window
+ * The DOM Window object.
+ *
+ * @return {Sandbox}
+ * The created sandbox.
+ */
+sandbox.createMutable = function(window) {
+ let opts = {
+ wantComponents: false,
+ wantXrays: false,
+ };
+ return sandbox.create(window, null, opts);
+};
+
+sandbox.createSystemPrincipal = function(window) {
+ let principal = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+ return sandbox.create(window, principal);
+};
+
+sandbox.createSimpleTest = function(window, harness) {
+ let sb = sandbox.create(window);
+ sb = sandbox.augment(sb, harness);
+ sb[FINISH] = () => sb[COMPLETE](harness.generate_results());
+ return sb;
+};
+
+/**
+ * Sandbox storage. When the user requests a sandbox by a specific name,
+ * if one exists in the storage this will be used as long as its window
+ * reference is still valid.
+ */
+this.Sandboxes = class {
+ /**
+ * @param {function(): Window} windowFn
+ * A function that returns the references to the current Window
+ * object.
+ */
+ constructor(windowFn) {
+ this.windowFn_ = windowFn;
+ this.boxes_ = new Map();
+ }
+
+ get window_() {
+ return this.windowFn_();
+ }
+
+ /**
+ * Factory function for getting a sandbox by name, or failing that,
+ * creating a new one.
+ *
+ * If the sandbox' window does not match the provided window, a new one
+ * will be created.
+ *
+ * @param {string} name
+ * The name of the sandbox to get or create.
+ * @param {boolean} fresh
+ * Remove old sandbox by name first, if it exists.
+ *
+ * @return {Sandbox}
+ * A used or fresh sandbox.
+ */
+ get(name = "default", fresh = false) {
+ let sb = this.boxes_.get(name);
+ if (sb) {
+ if (fresh || sb.window != this.window_) {
+ this.boxes_.delete(name);
+ return this.get(name, false);
+ }
+ } else {
+ if (name == "system") {
+ sb = sandbox.createSystemPrincipal(this.window_);
+ } else {
+ sb = sandbox.create(this.window_);
+ }
+ this.boxes_.set(name, sb);
+ }
+ return sb;
+ }
+
+ /**
+ * Clears cache of sandboxes.
+ */
+ clear() {
+ this.boxes_.clear();
+ }
+};
+
+/**
* Stores scripts imported from the local end through the
* {@code GeckoDriver#importScript} command.
*
* Imported scripts are prepended to the script that is evaluated
* on each call to {@code GeckoDriver#executeScript},
* {@code GeckoDriver#executeAsyncScript}, and
* {@code GeckoDriver#executeJSScript}.
*
--- a/testing/marionette/harness/marionette/marionette_test.py
+++ b/testing/marionette/harness/marionette/marionette_test.py
@@ -736,25 +736,27 @@ class MarionetteTestCase(CommonTestCase)
filepath=filepath,
testvars=testvars,
**kwargs))
def setUp(self):
CommonTestCase.setUp(self)
self.marionette.test_name = self.test_name
self.marionette.execute_script("log('TEST-START: %s:%s')" %
- (self.filepath.replace('\\', '\\\\'), self.methodName))
+ (self.filepath.replace('\\', '\\\\'), self.methodName),
+ sandbox="simpletest")
def tearDown(self):
if not self.marionette.check_for_crash():
try:
self.marionette.clear_imported_scripts()
self.marionette.execute_script("log('TEST-END: %s:%s')" %
(self.filepath.replace('\\', '\\\\'),
- self.methodName))
+ self.methodName),
+ sandbox="simpletest")
self.marionette.test_name = None
except (MarionetteException, IOError):
# We have tried to log the test end when there is no listener
# object that we can access
pass
CommonTestCase.tearDown(self)
@@ -797,21 +799,25 @@ class MarionetteJSTestCase(CommonTestCas
@classmethod
def add_tests_to_suite(cls, mod_name, filepath, suite, testloader, marionette, testvars, **kwargs):
suite.addTest(cls(weakref.ref(marionette), jsFile=filepath, **kwargs))
def runTest(self):
if self.marionette.session is None:
self.marionette.start_session()
- self.marionette.execute_script("log('TEST-START: %s');" % self.jsFile.replace('\\', '\\\\'))
+ self.marionette.execute_script(
+ "log('TEST-START: %s');" % self.jsFile.replace('\\', '\\\\'),
+ sandbox="simpletest")
self.run_js_test(self.jsFile)
- self.marionette.execute_script("log('TEST-END: %s');" % self.jsFile.replace('\\', '\\\\'))
+ self.marionette.execute_script(
+ "log('TEST-END: %s');" % self.jsFile.replace('\\', '\\\\'),
+ sandbox="simpletest")
self.marionette.test_name = None
def get_test_class_name(self):
# returns a dot separated folders as class name
dirname = os.path.dirname(self.jsFile).replace('\\', '/')
if dirname.startswith('/'):
dirname = dirname[1:]
return '.'.join(dirname.split('/'))
--- a/testing/marionette/harness/marionette/tests/unit/test_click_chrome.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_click_chrome.py
@@ -4,29 +4,31 @@
from marionette import MarionetteTestCase
from marionette_driver.by import By
class TestClickChrome(MarionetteTestCase):
def setUp(self):
MarionetteTestCase.setUp(self)
+ self.root_window = self.marionette.current_window_handle
self.marionette.set_context("chrome")
- self.win = self.marionette.current_window_handle
- self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
- self.marionette.switch_to_window('foo')
- self.assertNotEqual(self.win, self.marionette.current_window_handle)
+ self.marionette.execute_script(
+ "window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen')")
+ self.marionette.switch_to_window("foo")
+ self.assertNotEqual(self.root_window, self.marionette.current_window_handle)
def tearDown(self):
- self.assertNotEqual(self.win, self.marionette.current_window_handle)
- self.marionette.execute_script("window.close();")
- self.marionette.switch_to_window(self.win)
+ self.assertNotEqual(self.root_window, self.marionette.current_window_handle)
+ self.marionette.execute_script("window.close()")
+ self.marionette.switch_to_window(self.root_window)
MarionetteTestCase.tearDown(self)
def test_click(self):
- wins = self.marionette.window_handles
- wins.remove(self.win)
- newWin = wins.pop()
- self.marionette.switch_to_window(newWin)
+ def checked():
+ return self.marionette.execute_script(
+ "return arguments[0].checked",
+ script_args=[box])
+
box = self.marionette.find_element(By.ID, "testBox")
- self.assertFalse(self.marionette.execute_script("return arguments[0].checked;", [box]))
+ self.assertFalse(checked())
box.click()
- self.assertTrue(self.marionette.execute_script("return arguments[0].checked;", [box]))
+ self.assertTrue(checked())
--- a/testing/marionette/harness/marionette/tests/unit/test_execute_sandboxes.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_execute_sandboxes.py
@@ -6,67 +6,73 @@ from marionette import MarionetteTestCas
from marionette_driver.errors import JavascriptException
class TestExecuteSandboxes(MarionetteTestCase):
def setUp(self):
super(TestExecuteSandboxes, self).setUp()
def test_execute_system_sandbox(self):
- # Test that 'system' sandbox has elevated privileges in execute_script
- result = self.marionette.execute_script("""
- return Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
- """, sandbox='system')
+ # Test that "system" sandbox has elevated privileges in execute_script
+ result = self.marionette.execute_script(
+ "return Components.interfaces.nsIPermissionManager.ALLOW_ACTION",
+ sandbox="system")
self.assertEqual(result, 1)
def test_execute_async_system_sandbox(self):
- # Test that 'system' sandbox has elevated privileges in
+ # Test that "system" sandbox has elevated privileges in
# execute_async_script.
result = self.marionette.execute_async_script("""
- let result = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
- marionetteScriptFinished(result);
- """, sandbox='system')
+ const Ci = Components.interfaces;
+ let result = Ci.nsIPermissionManager.ALLOW_ACTION;
+ marionetteScriptFinished(result);""",
+ sandbox="system")
self.assertEqual(result, 1)
def test_execute_switch_sandboxes(self):
# Test that sandboxes are retained when switching between them
# for execute_script.
- self.marionette.execute_script("foo = 1;", sandbox='1')
- self.marionette.execute_script("foo = 2;", sandbox='2')
- foo = self.marionette.execute_script("return foo;", sandbox='1',
- new_sandbox=False)
+ self.marionette.execute_script("foo = 1", sandbox="1")
+ self.marionette.execute_script("foo = 2", sandbox="2")
+ foo = self.marionette.execute_script(
+ "return foo", sandbox="1", new_sandbox=False)
self.assertEqual(foo, 1)
- foo = self.marionette.execute_script("return foo;", sandbox='2',
- new_sandbox=False)
+ foo = self.marionette.execute_script(
+ "return foo", sandbox="2", new_sandbox=False)
self.assertEqual(foo, 2)
def test_execute_new_sandbox(self):
- # Test that clearing a sandbox does not affect other sandboxes
- self.marionette.execute_script("foo = 1;", sandbox='1')
- self.marionette.execute_script("foo = 2;", sandbox='2')
- self.assertRaises(JavascriptException,
- self.marionette.execute_script,
- "return foo;", sandbox='1', new_sandbox=True)
- foo = self.marionette.execute_script("return foo;", sandbox='2',
- new_sandbox=False)
+ # test that clearing a sandbox does not affect other sandboxes
+ self.marionette.execute_script("foo = 1", sandbox="1")
+ self.marionette.execute_script("foo = 2", sandbox="2")
+
+ # deprecate sandbox 1 by asking explicitly for a fresh one
+ with self.assertRaises(JavascriptException):
+ self.marionette.execute_script("return foo",
+ sandbox="1", new_sandbox=True)
+
+ foo = self.marionette.execute_script(
+ "return foo", sandbox="2", new_sandbox=False)
self.assertEqual(foo, 2)
def test_execute_async_switch_sandboxes(self):
# Test that sandboxes are retained when switching between them
# for execute_async_script.
- self.marionette.execute_async_script("foo = 1; marionetteScriptFinished()",
- sandbox='1')
- self.marionette.execute_async_script("foo = 2; marionetteScriptFinished()",
- sandbox='2')
- foo = self.marionette.execute_async_script("marionetteScriptFinished(foo);",
- sandbox='1',
- new_sandbox=False)
+ self.marionette.execute_async_script(
+ "foo = 1; marionetteScriptFinished()", sandbox="1")
+ self.marionette.execute_async_script(
+ "foo = 2; marionetteScriptFinished()", sandbox='2')
+ foo = self.marionette.execute_async_script(
+ "marionetteScriptFinished(foo)",
+ sandbox="1",
+ new_sandbox=False)
self.assertEqual(foo, 1)
- foo = self.marionette.execute_async_script("marionetteScriptFinished(foo);",
- sandbox='2',
- new_sandbox=False)
+ foo = self.marionette.execute_async_script(
+ "marionetteScriptFinished(foo)",
+ sandbox="2",
+ new_sandbox=False)
self.assertEqual(foo, 2)
class TestExecuteSandboxesChrome(TestExecuteSandboxes):
def setUp(self):
super(TestExecuteSandboxesChrome, self).setUp()
self.marionette.set_context("chrome")
--- a/testing/marionette/harness/marionette/tests/unit/test_execute_script.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_execute_script.py
@@ -10,113 +10,232 @@ from marionette import MarionetteTestCas
def inline(doc):
return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
elements = inline("<p>foo</p> <p>bar</p>")
+globals = set([
+ "atob",
+ "Audio",
+ "btoa",
+ "document",
+ "navigator",
+ "URL",
+ "window",
+ ])
-class TestExecuteContent(MarionetteTestCase):
- def test_execute_js_script_stack_trace(self):
+
+class TestExecuteSimpleTestContent(MarionetteTestCase):
+ def test_stack_trace(self):
try:
self.marionette.execute_js_script("""
let a = 1;
throwHere();
""", filename="file.js")
self.assertFalse(True)
- except errors.JavascriptException as inst:
- self.assertIn('throwHere is not defined', inst.msg)
- self.assertIn('@file.js:2', inst.stacktrace)
+ except errors.JavascriptException as e:
+ self.assertIn("throwHere is not defined", e.msg)
+ self.assertIn("@file.js:2", e.stacktrace)
+
+
+class TestExecuteContent(MarionetteTestCase):
+ def test_return_number(self):
+ self.assertEqual(1, self.marionette.execute_script("return 1"))
+ self.assertEqual(1.5, self.marionette.execute_script("return 1.5"))
+
+ def test_return_boolean(self):
+ self.assertEqual(True, self.marionette.execute_script("return true"))
+
+ def test_return_string(self):
+ self.assertEqual("foo", self.marionette.execute_script("return 'foo'"))
+
+ def test_return_array(self):
+ self.assertEqual(
+ [1, 2], self.marionette.execute_script("return [1, 2]"))
+ self.assertEqual(
+ [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]"))
+ self.assertEqual(
+ [True, False], self.marionette.execute_script("return [true, false]"))
+ self.assertEqual(
+ ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']"))
+ self.assertEqual(
+ [1, 1.5, True, "foo"], self.marionette.execute_script("return [1, 1.5, true, 'foo']"))
+ self.assertEqual(
+ [1, [2]], self.marionette.execute_script("return [1, [2]]"))
- def test_execute_script_stack_trace(self):
- try:
- self.marionette.execute_script("""
- let a = 1;
- return b;
- """)
- self.assertFalse(True)
- except errors.JavascriptException as inst:
- # By default execute_script pass the name of the python file
- self.assertIn(os.path.basename(__file__.replace(".pyc", ".py")), inst.stacktrace)
- self.assertIn('b is not defined', inst.msg)
- self.assertIn('return b', inst.stacktrace)
+ def test_return_object(self):
+ self.assertEqual(
+ {"foo": 1}, self.marionette.execute_script("return {foo: 1}"))
+ self.assertEqual(
+ {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}"))
+ self.assertEqual(
+ {"foo": True}, self.marionette.execute_script("return {foo: true}"))
+ self.assertEqual(
+ {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}"))
+ self.assertEqual(
+ {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}"))
+ self.assertEqual(
+ {"foo": {"bar": [1, 2]}}, self.marionette.execute_script("return {foo: {bar: [1, 2]}}"))
- def test_execute_simple(self):
- self.assertEqual(1, self.marionette.execute_script("return 1;"))
+ def test_no_return_value(self):
+ self.assertIsNone(self.marionette.execute_script("true"))
+
+ def test_argument_null(self):
+ self.assertEqual(
+ None, self.marionette.execute_script("return arguments[0]", [None]))
- def test_check_window(self):
- self.assertTrue(self.marionette.execute_script("return (window !=null && window != undefined);"))
+ def test_argument_number(self):
+ self.assertEqual(
+ 1, self.marionette.execute_script("return arguments[0]", [1]))
+ self.assertEqual(
+ 1.5, self.marionette.execute_script("return arguments[0]", [1.5]))
+
+ def test_argument_boolean(self):
+ self.assertEqual(
+ True, self.marionette.execute_script("return arguments[0]", [True]))
- def test_execute_no_return(self):
- self.assertEqual(self.marionette.execute_script("1;"), None)
+ def test_argument_string(self):
+ self.assertEqual(
+ "foo", self.marionette.execute_script("return arguments[0]", ["foo"]))
+
+ def test_argument_array(self):
+ self.assertEqual(
+ [1, 2], self.marionette.execute_script("return arguments[0]", [[1, 2]]))
+
+ def test_argument_object(self):
+ self.assertEqual({"foo": 1}, self.marionette.execute_script(
+ "return arguments[0]", [{"foo": 1}]))
- def test_execute_js_exception(self):
- self.assertRaises(errors.JavascriptException,
- self.marionette.execute_script, "return foo(bar);")
+ def assert_is_defined(self, property, sandbox="default"):
+ self.assertTrue(self.marionette.execute_script(
+ "return typeof %s != 'undefined'" % property,
+ sandbox=sandbox),
+ "property %s is undefined" % property)
- def test_execute_permission(self):
+ def test_globals(self):
+ for property in globals:
+ self.assert_is_defined(property)
+ self.assert_is_defined("Components")
+ self.assert_is_defined("window.wrappedJSObject")
+
+ def test_system_globals(self):
+ for property in globals:
+ self.assert_is_defined(property, sandbox="system")
+ self.assert_is_defined("Components", sandbox="system")
+ self.assert_is_defined("window.wrappedJSObject")
+
+ def test_exception(self):
self.assertRaises(errors.JavascriptException,
- self.marionette.execute_script,
- """
-let prefs = Components.classes["@mozilla.org/preferences-service;1"]
- .getService(Components.interfaces.nsIPrefBranch);
-""")
+ self.marionette.execute_script, "return foo")
+
+ def test_stacktrace(self):
+ try:
+ self.marionette.execute_script("return b")
+ self.assertFalse(True)
+ except errors.JavascriptException as e:
+ # by default execute_script pass the name of the python file
+ self.assertIn(
+ os.path.basename(__file__.replace(".pyc", ".py")), e.stacktrace)
+ self.assertIn("b is not defined", e.msg)
+ self.assertIn("return b", e.stacktrace)
- def test_complex_return_values(self):
- self.assertEqual(self.marionette.execute_script("return [1, 2];"), [1, 2])
- self.assertEqual(self.marionette.execute_script("return {'foo': 'bar', 'fizz': 'fazz'};"),
- {'foo': 'bar', 'fizz': 'fazz'})
- self.assertEqual(self.marionette.execute_script("return [1, {'foo': 'bar'}, 2];"),
- [1, {'foo': 'bar'}, 2])
- self.assertEqual(self.marionette.execute_script("return {'foo': [1, 'a', 2]};"),
- {'foo': [1, 'a', 2]})
+ def test_permission(self):
+ with self.assertRaises(errors.JavascriptException):
+ self.marionette.execute_script("""
+ let prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch)""")
+
+ def test_return_web_element(self):
+ self.marionette.navigate(elements)
+ expected = self.marionette.find_element(By.TAG_NAME, "p")
+ actual = self.marionette.execute_script(
+ "return document.querySelector('p')")
+ self.assertEqual(expected, actual)
+
+ def test_return_web_element_array(self):
+ self.marionette.navigate(elements)
+ expected = self.marionette.find_elements(By.TAG_NAME, "p")
+ actual = self.marionette.execute_script("""
+ let els = document.querySelectorAll('p')
+ return [els[0], els[1]]""")
+ self.assertEqual(expected, actual)
# Bug 938228 identifies a problem with unmarshaling NodeList
# objects from the DOM. document.querySelectorAll returns this
# construct.
- def test_unmarshal_element_collection(self):
+ def test_return_web_element_nodelist(self):
self.marionette.navigate(elements)
expected = self.marionette.find_elements(By.TAG_NAME, "p")
actual = self.marionette.execute_script(
"return document.querySelectorAll('p')")
self.assertEqual(expected, actual)
def test_sandbox_reuse(self):
# Sandboxes between `execute_script()` invocations are shared.
self.marionette.execute_script("this.foobar = [23, 42];")
- self.assertEqual(self.marionette.execute_script("return this.foobar;", new_sandbox=False), [23, 42])
+ self.assertEqual(self.marionette.execute_script(
+ "return this.foobar;", new_sandbox=False), [23, 42])
self.marionette.execute_script("global.barfoo = [42, 23];")
- self.assertEqual(self.marionette.execute_script("return global.barfoo;", new_sandbox=False), [42, 23])
+ self.assertEqual(self.marionette.execute_script(
+ "return global.barfoo;", new_sandbox=False), [42, 23])
def test_sandbox_refresh_arguments(self):
- self.marionette.execute_script("this.foobar = [arguments[0], arguments[1]];",
- script_args=[23, 42])
- self.assertEqual(self.marionette.execute_script("return this.foobar;", new_sandbox=False),
- [23, 42])
+ self.marionette.execute_script(
+ "this.foobar = [arguments[0], arguments[1]]", [23, 42])
+ self.assertEqual(self.marionette.execute_script(
+ "return this.foobar", new_sandbox=False), [23, 42])
+
+ def test_wrappedjsobject(self):
+ self.marionette.execute_script("window.wrappedJSObject.foo = 3")
+ self.assertEqual(
+ 3, self.marionette.execute_script("return window.wrappedJSObject.foo"))
- self.marionette.execute_script("global.barfoo = [arguments[0], arguments[1]];",
- script_args=[42, 23],
- new_sandbox=False)
- self.assertEqual(self.marionette.execute_script("return global.barfoo;", new_sandbox=False),
- [42, 23])
+ def test_system_sandbox_wrappedjsobject(self):
+ self.marionette.execute_script(
+ "window.wrappedJSObject.foo = 4", sandbox="system")
+ self.assertEqual(4, self.marionette.execute_script(
+ "return window.wrappedJSObject.foo", sandbox="system"))
+
+ def test_system_dead_object(self):
+ self.marionette.execute_script(
+ "window.wrappedJSObject.foo = function() { return 'yo' }",
+ sandbox="system")
+ self.marionette.execute_script(
+ "dump(window.wrappedJSObject.foo)", sandbox="system")
- def test_that_we_can_pass_in_floats(self):
- expected_result = 1.2
- result = self.marionette.execute_script("return arguments[0]",
- [expected_result])
- self.assertTrue(isinstance(result, float))
- self.assertEqual(result, expected_result)
+ self.marionette.execute_script(
+ "window.wrappedJSObject.foo = function() { return 'yolo' }",
+ sandbox="system")
+ typ = self.marionette.execute_script(
+ "return typeof window.wrappedJSObject.foo", sandbox="system")
+ self.assertEqual("function", typ)
+ obj = self.marionette.execute_script(
+ "return window.wrappedJSObject.foo.toString()", sandbox="system")
+ self.assertIn("yolo", obj)
- def test_null_argument(self):
- result = self.marionette.execute_script("return arguments[0]",
- [None])
- self.assertIs(result, None)
+ def test_lasting_side_effects(self):
+ def send(script):
+ return self.marionette._send_message(
+ "executeScript", {"script": script}, key="value")
+
+ send("window.foo = 1")
+ foo = send("return window.foo")
+ self.assertEqual(1, foo)
+
+ for property in globals:
+ exists = send("return typeof %s != 'undefined'" % property)
+ self.assertTrue(exists, "property %s is undefined" % property)
+ # TODO(ato): For some reason this fails, probably Sandbox bug?
+ # self.assertTrue(send("return typeof Components == 'undefined'"))
+ self.assertTrue(
+ send("return typeof window.wrappedJSObject == 'undefined'"))
@skip_if_b2g
class TestExecuteChrome(TestExecuteContent):
def setUp(self):
super(TestExecuteChrome, self).setUp()
self.win = self.marionette.current_window_handle
self.marionette.set_context("chrome")
--- a/testing/marionette/harness/marionette/tests/unit/test_log.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_log.py
@@ -1,47 +1,64 @@
# 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/.
from marionette import MarionetteTestCase
class TestLog(MarionetteTestCase):
- def test_log_basic(self):
- # clear any previous data
- self.marionette.get_logs()
-
- self.marionette.log("I am info")
- self.assertTrue("I am info" in self.marionette.get_logs()[0])
- self.marionette.log("I AM ERROR", "ERROR")
- self.assertTrue("I AM ERROR" in self.marionette.get_logs()[0])
-
- def test_that_we_can_clear_the_logs(self):
- # clear any previous data
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ # clears log cache
self.marionette.get_logs()
- self.marionette.log("I am info")
- self.assertTrue("I am info" in self.marionette.get_logs()[0])
- self.marionette.log("I AM ERROR", "ERROR")
- self.assertTrue("I AM ERROR" in self.marionette.get_logs()[0])
+ def test_log(self):
+ self.marionette.log("foo")
+ logs = self.marionette.get_logs()
+ self.assertEqual("INFO", logs[0][0])
+ self.assertEqual("foo", logs[0][1])
- # Check that is empty if we call it again
+ def test_log_level(self):
+ self.marionette.log("foo", "ERROR")
+ logs = self.marionette.get_logs()
+ self.assertEqual("ERROR", logs[0][0])
+ self.assertEqual("foo", logs[0][1])
+
+ def test_clear(self):
+ self.marionette.log("foo")
+ self.assertEqual(1, len(self.marionette.get_logs()))
self.assertEqual(0, len(self.marionette.get_logs()))
- def test_log_script(self):
- # clear any previous data
- self.marionette.get_logs()
+ def test_multiple_entries(self):
+ self.marionette.log("foo")
+ self.marionette.log("bar")
+ self.assertEqual(2, len(self.marionette.get_logs()))
+
+ def test_log_from_sync_script(self):
+ self.marionette.execute_script("log('foo')")
+ logs = self.marionette.get_logs()
+ self.assertEqual("INFO", logs[0][0])
+ self.assertEqual("foo", logs[0][1])
- self.marionette.execute_script("log('some log');")
- self.assertTrue("some log" in self.marionette.get_logs()[0])
- self.marionette.execute_script("log('some error', 'ERROR');")
- self.assertTrue("some error" in self.marionette.get_logs()[0])
- self.marionette.set_script_timeout(2000)
- self.marionette.execute_async_script("log('some more logs'); finish();")
- self.assertTrue("some more logs" in self.marionette.get_logs()[0])
- self.marionette.execute_async_script("log('some more errors', 'ERROR'); finish();")
- self.assertTrue("some more errors" in self.marionette.get_logs()[0])
+ def test_log_from_sync_script_level(self):
+ self.marionette.execute_script("log('foo', 'ERROR')")
+ logs = self.marionette.get_logs()
+ self.assertEqual("ERROR", logs[0][0])
+ self.assertEqual("foo", logs[0][1])
+
+ def test_log_from_async_script(self):
+ self.marionette.execute_async_script("log('foo'); arguments[0]();")
+ logs = self.marionette.get_logs()
+ self.assertEqual("INFO", logs[0][0])
+ self.assertEqual("foo", logs[0][1])
+
+ def test_log_from_async_script_variable_arguments(self):
+ self.marionette.execute_async_script("log('foo', 'ERROR'); arguments[0]();")
+ logs = self.marionette.get_logs()
+ self.assertEqual("ERROR", logs[0][0])
+ self.assertEqual("foo", logs[0][1])
+
class TestLogChrome(TestLog):
def setUp(self):
- MarionetteTestCase.setUp(self)
+ TestLog.setUp(self)
self.marionette.set_context("chrome")
--- a/testing/marionette/harness/marionette/tests/unit/test_simpletest_sanity.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_simpletest_sanity.py
@@ -1,101 +1,106 @@
# 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/.
from marionette import MarionetteTestCase
class SimpletestSanityTest(MarionetteTestCase):
+ callFinish = "return finish();"
- callFinish = "return finish();"
+ def run_sync(self, test):
+ return self.marionette.execute_js_script(test, async=False)
+
+ def run_async(self, test):
+ return self.marionette.execute_js_script(test)
def test_is(self):
def runtests():
sentFail1 = "is(true, false, 'isTest1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
sentFail2 = "is(true, false, 'isTest2', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
sentPass1 = "is(true, true, 'isTest3');" + self.callFinish
sentPass2 = "is(true, true, 'isTest4');" + self.callFinish
- self.assertEqual(1, len(self.marionette.execute_script(sentFail1)["failures"]))
- self.assertEqual(0, self.marionette.execute_script(sentFail2)["passed"])
- self.assertEqual(1, self.marionette.execute_script(sentPass1)["passed"])
- self.assertEqual(0, len(self.marionette.execute_script(sentPass2)["failures"]))
+ self.assertEqual(1, len(self.run_sync(sentFail1)["failures"]))
+ self.assertEqual(0, self.run_sync(sentFail2)["passed"])
+ self.assertEqual(1, self.run_sync(sentPass1)["passed"])
+ self.assertEqual(0, len(self.run_sync(sentPass2)["failures"]))
self.marionette.set_script_timeout(1000)
- self.assertEqual(1, len(self.marionette.execute_async_script(sentFail1)["failures"]))
- self.assertEqual(0, self.marionette.execute_async_script(sentFail2)["passed"])
- self.assertEqual(1, self.marionette.execute_async_script(sentPass1)["passed"])
- self.assertEqual(0, len(self.marionette.execute_async_script(sentPass2)["failures"]))
+ self.assertEqual(1, len(self.run_async(sentFail1)["failures"]))
+ self.assertEqual(0, self.run_async(sentFail2)["passed"])
+ self.assertEqual(1, self.run_async(sentPass1)["passed"])
+ self.assertEqual(0, len(self.run_async(sentPass2)["failures"]))
self.marionette.set_context("content")
runtests()
self.marionette.set_context("chrome")
runtests()
def test_isnot(self):
def runtests():
sentFail1 = "isnot(true, true, 'isnotTest3', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
sentFail2 = "isnot(true, true, 'isnotTest4', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
sentPass1 = "isnot(true, false, 'isnotTest1');" + self.callFinish
sentPass2 = "isnot(true, false, 'isnotTest2');" + self.callFinish
- self.assertEqual(1, len(self.marionette.execute_script(sentFail1)["failures"]));
- self.assertEqual(0, self.marionette.execute_script(sentFail2)["passed"]);
- self.assertEqual(0, len(self.marionette.execute_script(sentPass1)["failures"]));
- self.assertEqual(1, self.marionette.execute_script(sentPass2)["passed"]);
+ self.assertEqual(1, len(self.run_sync(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_sync(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_sync(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_sync(sentPass2)["passed"]);
self.marionette.set_script_timeout(1000)
- self.assertEqual(1, len(self.marionette.execute_async_script(sentFail1)["failures"]));
- self.assertEqual(0, self.marionette.execute_async_script(sentFail2)["passed"]);
- self.assertEqual(0, len(self.marionette.execute_async_script(sentPass1)["failures"]));
- self.assertEqual(1, self.marionette.execute_async_script(sentPass2)["passed"]);
+ self.assertEqual(1, len(self.run_async(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_async(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_async(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_async(sentPass2)["passed"]);
self.marionette.set_context("content")
runtests()
self.marionette.set_context("chrome")
runtests()
def test_ok(self):
def runtests():
sentFail1 = "ok(1==2, 'testOk1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
sentFail2 = "ok(1==2, 'testOk2', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
sentPass1 = "ok(1==1, 'testOk3');" + self.callFinish
sentPass2 = "ok(1==1, 'testOk4');" + self.callFinish
- self.assertEqual(1, len(self.marionette.execute_script(sentFail1)["failures"]));
- self.assertEqual(0, self.marionette.execute_script(sentFail2)["passed"]);
- self.assertEqual(0, len(self.marionette.execute_script(sentPass1)["failures"]));
- self.assertEqual(1, self.marionette.execute_script(sentPass2)["passed"]);
+ self.assertEqual(1, len(self.run_sync(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_sync(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_sync(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_sync(sentPass2)["passed"]);
self.marionette.set_script_timeout(1000)
- self.assertEqual(1, len(self.marionette.execute_async_script(sentFail1)["failures"]));
- self.assertEqual(0, self.marionette.execute_async_script(sentFail2)["passed"]);
- self.assertEqual(0, len(self.marionette.execute_async_script(sentPass1)["failures"]));
- self.assertEqual(1, self.marionette.execute_async_script(sentPass2)["passed"]);
+ self.assertEqual(1, len(self.run_async(sentFail1)["failures"]));
+ self.assertEqual(0, self.run_async(sentFail2)["passed"]);
+ self.assertEqual(0, len(self.run_async(sentPass1)["failures"]));
+ self.assertEqual(1, self.run_async(sentPass2)["passed"]);
self.marionette.set_context("content")
runtests()
self.marionette.set_context("chrome")
runtests()
def test_todo(self):
def runtests():
sentFail1 = "todo(1==1, 'testTodo1', TEST_UNEXPECTED_PASS, TEST_KNOWN_FAIL);" + self.callFinish
sentFail2 = "todo(1==1, 'testTodo2', TEST_UNEXPECTED_PASS, TEST_KNOWN_FAIL);" + self.callFinish
sentPass1 = "todo(1==2, 'testTodo3');" + self.callFinish
sentPass2 = "todo(1==2, 'testTodo4');" + self.callFinish
- self.assertEqual(1, len(self.marionette.execute_script(sentFail1)["unexpectedSuccesses"]));
- self.assertEqual(0, len(self.marionette.execute_script(sentFail2)["expectedFailures"]));
- self.assertEqual(0, len(self.marionette.execute_script(sentPass1)["unexpectedSuccesses"]));
- self.assertEqual(1, len(self.marionette.execute_script(sentPass2)["expectedFailures"]));
+ self.assertEqual(1, len(self.run_sync(sentFail1)["unexpectedSuccesses"]));
+ self.assertEqual(0, len(self.run_sync(sentFail2)["expectedFailures"]));
+ self.assertEqual(0, len(self.run_sync(sentPass1)["unexpectedSuccesses"]));
+ self.assertEqual(1, len(self.run_sync(sentPass2)["expectedFailures"]));
self.marionette.set_script_timeout(1000)
- self.assertEqual(1, len(self.marionette.execute_async_script(sentFail1)["unexpectedSuccesses"]));
- self.assertEqual(0, len(self.marionette.execute_async_script(sentFail2)["expectedFailures"]));
- self.assertEqual(0, len(self.marionette.execute_async_script(sentPass1)["unexpectedSuccesses"]));
- self.assertEqual(1, len(self.marionette.execute_async_script(sentPass2)["expectedFailures"]));
+ self.assertEqual(1, len(self.run_async(sentFail1)["unexpectedSuccesses"]));
+ self.assertEqual(0, len(self.run_async(sentFail2)["expectedFailures"]));
+ self.assertEqual(0, len(self.run_async(sentPass1)["unexpectedSuccesses"]));
+ self.assertEqual(1, len(self.run_async(sentPass2)["expectedFailures"]));
self.marionette.set_context("content")
runtests()
self.marionette.set_context("chrome")
runtests()
--- a/testing/marionette/jar.mn
+++ b/testing/marionette/jar.mn
@@ -6,30 +6,30 @@ marionette.jar:
% content marionette %content/
content/server.js (server.js)
content/driver.js (driver.js)
content/action.js (action.js)
content/interaction.js (interaction.js)
content/accessibility.js (accessibility.js)
content/listener.js (listener.js)
content/element.js (element.js)
- content/common.js (common.js)
content/simpletest.js (simpletest.js)
content/frame.js (frame.js)
content/event.js (event.js)
content/error.js (error.js)
content/message.js (message.js)
content/dispatcher.js (dispatcher.js)
content/emulator.js (emulator.js)
content/modal.js (modal.js)
content/proxy.js (proxy.js)
content/capture.js (capture.js)
content/cookies.js (cookies.js)
content/atom.js (atom.js)
content/evaluate.js (evaluate.js)
+ content/logging.js (logging.js)
#ifdef ENABLE_TESTS
content/test.xul (harness/marionette/chrome/test.xul)
content/test2.xul (harness/marionette/chrome/test2.xul)
content/test_nested_iframe.xul (harness/marionette/chrome/test_nested_iframe.xul)
content/test_anonymous_content.xul (harness/marionette/chrome/test_anonymous_content.xul)
#endif
% content specialpowers %content/
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -1,104 +1,110 @@
/* 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/. */
+"use strict";
+
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
var uuidGen = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
-loader.loadSubScript("chrome://marionette/content/simpletest.js");
-loader.loadSubScript("chrome://marionette/content/common.js");
-
-Cu.import("resource://gre/modules/Task.jsm");
-
Cu.import("chrome://marionette/content/accessibility.js");
Cu.import("chrome://marionette/content/action.js");
Cu.import("chrome://marionette/content/atom.js");
Cu.import("chrome://marionette/content/capture.js");
Cu.import("chrome://marionette/content/cookies.js");
Cu.import("chrome://marionette/content/element.js");
+Cu.import("chrome://marionette/content/emulator.js");
Cu.import("chrome://marionette/content/error.js");
Cu.import("chrome://marionette/content/evaluate.js");
Cu.import("chrome://marionette/content/event.js");
Cu.import("chrome://marionette/content/interaction.js");
+Cu.import("chrome://marionette/content/logging.js");
Cu.import("chrome://marionette/content/proxy.js");
+Cu.import("chrome://marionette/content/simpletest.js");
-var marionetteLogObj = new MarionetteLogObj();
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var contentLog = new logging.ContentLogger();
var isB2G = false;
var marionetteTestName;
var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
var listenerId = null; // unique ID of this listener
var curContainer = { frame: content, shadowRoot: null };
var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
var previousContainer = null;
var elementManager = new ElementManager();
-
var capabilities = {};
var actions = new action.Chain(checkForInterrupted);
// Contains the last file input element that was the target of
// sendKeysToElement.
var fileInputElement;
-// A dict of sandboxes used this session
-var sandboxes = {};
-// The name of the current sandbox
-var sandboxName = 'default';
-
// the unload handler
var onunload;
// Flag to indicate whether an async script is currently running or not.
var asyncTestRunning = false;
var asyncTestCommandId;
var asyncTestTimeoutId;
var inactivityTimeoutId = null;
-var heartbeatCallback = function () {}; // Called by the simpletest methods.
var originalOnError;
//timer for doc changes
var checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
//timer for readystate
var readyStateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// timer for navigation commands.
var navTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
var onDOMContentLoaded;
// Send move events about this often
var EVENT_INTERVAL = 30; // milliseconds
// last touch for each fingerId
var multiLast = {};
-
-var chrome = proxy.toChrome(sendSyncMessage.bind(this));
-var cookies = new Cookies(() => curContainer.frame.document, chrome);
-var importedScripts = new evaluate.ScriptStorageServiceClient(chrome);
+var asyncChrome = proxy.toChromeAsync({
+ addMessageListener: addMessageListenerId.bind(this),
+ removeMessageListener: removeMessageListenerId.bind(this),
+ sendAsyncMessage: sendAsyncMessage.bind(this),
+});
+var syncChrome = proxy.toChrome(sendSyncMessage.bind(this));
+var cookies = new Cookies(() => curContainer.frame.document, syncChrome);
+var importedScripts = new evaluate.ScriptStorageServiceClient(syncChrome);
Cu.import("resource://gre/modules/Log.jsm");
var logger = Log.repository.getLogger("Marionette");
logger.debug("loaded listener.js");
+
var modalHandler = function() {
// This gets called on the system app only since it receives the mozbrowserprompt event
- sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
+ sendSyncMessage("Marionette:switchedToFrame", {frameValue: null, storePrevious: true});
let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
if (isLocal) {
previousContainer = curContainer;
}
- curContainer = { frame: content, shadowRoot: null };
+ curContainer = {frame: content, shadowRoot: null};
};
+// sandbox storage and name of the current sandbox
+var sandboxes = new Sandboxes(() => curContainer.frame);
+var sandboxName = "default";
+
/**
* Called when listener is first started up.
* The listener sends its unique window ID and its current URI to the actor.
* If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
*/
function registerSelf() {
let msg = {value: winUtil.outerWindowID};
// register will have the ID and a boolean describing if this is the main process or not
@@ -223,26 +229,29 @@ var getCookiesFn = dispatch(getCookies);
var singleTapFn = dispatch(singleTap);
var takeScreenshotFn = dispatch(takeScreenshot);
var getScreenshotHashFn = dispatch(getScreenshotHash);
var actionChainFn = dispatch(actionChain);
var multiActionFn = dispatch(multiAction);
var addCookieFn = dispatch(addCookie);
var deleteCookieFn = dispatch(deleteCookie);
var deleteAllCookiesFn = dispatch(deleteAllCookies);
+var executeFn = dispatch(execute);
+var executeInSandboxFn = dispatch(executeInSandbox);
+var executeSimpleTestFn = dispatch(executeSimpleTest);
/**
* Start all message listeners
*/
function startListeners() {
addMessageListenerId("Marionette:receiveFiles", receiveFiles);
addMessageListenerId("Marionette:newSession", newSession);
- addMessageListenerId("Marionette:executeScript", executeScript);
- addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
- addMessageListenerId("Marionette:executeJSScript", executeJSScript);
+ addMessageListenerId("Marionette:execute", executeFn);
+ addMessageListenerId("Marionette:executeInSandbox", executeInSandboxFn);
+ addMessageListenerId("Marionette:executeSimpleTest", executeSimpleTestFn);
addMessageListenerId("Marionette:singleTap", singleTapFn);
addMessageListenerId("Marionette:actionChain", actionChainFn);
addMessageListenerId("Marionette:multiAction", multiActionFn);
addMessageListenerId("Marionette:get", get);
addMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
addMessageListenerId("Marionette:cancelRequest", cancelRequest);
addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrlFn);
addMessageListenerId("Marionette:getTitle", getTitleFn);
@@ -335,19 +344,19 @@ function restart(msg) {
}
/**
* Removes all listeners
*/
function deleteSession(msg) {
removeMessageListenerId("Marionette:receiveFiles", receiveFiles);
removeMessageListenerId("Marionette:newSession", newSession);
- removeMessageListenerId("Marionette:executeScript", executeScript);
- removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
- removeMessageListenerId("Marionette:executeJSScript", executeJSScript);
+ removeMessageListenerId("Marionette:execute", executeFn);
+ removeMessageListenerId("Marionette:executeInSandbox", executeInSandboxFn);
+ removeMessageListenerId("Marionette:executeSimpleTest", executeSimpleTestFn);
removeMessageListenerId("Marionette:singleTap", singleTapFn);
removeMessageListenerId("Marionette:actionChain", actionChainFn);
removeMessageListenerId("Marionette:multiAction", multiActionFn);
removeMessageListenerId("Marionette:get", get);
removeMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
removeMessageListenerId("Marionette:cancelRequest", cancelRequest);
removeMessageListenerId("Marionette:getTitle", getTitleFn);
removeMessageListenerId("Marionette:getPageSource", getPageSourceFn);
@@ -451,18 +460,18 @@ function sendError(err, uuid) {
function sendLog(msg) {
sendToServer("Marionette:log", {message: msg});
}
/**
* Clear test values after completion of test
*/
function resetValues() {
- sandboxes = {};
- curContainer = { frame: content, shadowRoot: null };
+ sandboxes.clear();
+ curContainer = {frame: content, shadowRoot: null};
actions.mouseEventsOnly = false;
}
/**
* Dump a logline to stdout. Prepends logline with a timestamp.
*/
function dumpLog(logline) {
dump(Date.now() + " Marionette: " + logline);
@@ -494,199 +503,79 @@ function checkForInterrupted() {
else {
//else we're in OOP environment, so we'll switch to the original OOP frame
sendSyncMessage("Marionette:switchToModalOrigin");
}
sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
}
}
-/*
- * Marionette Methods
- */
-
-/**
- * Returns a content sandbox that can be used by the execute_foo functions.
- */
-function createExecuteContentSandbox(win, timeout) {
- let mn = new Marionette(
- win,
- "content",
- marionetteLogObj,
- timeout,
- heartbeatCallback,
- marionetteTestName);
-
- let principal = win;
- if (sandboxName == "system") {
- principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
- }
- let sandbox = new Cu.Sandbox(principal, {sandboxPrototype: win});
- sandbox.global = sandbox;
- sandbox.window = win;
- sandbox.document = sandbox.window.document;
- sandbox.navigator = sandbox.window.navigator;
- sandbox.asyncTestCommandId = asyncTestCommandId;
- sandbox.marionette = mn;
-
- mn.exports.forEach(fn => {
- if (typeof mn[fn] == "function") {
- sandbox[fn] = mn[fn].bind(mn);
- } else {
- sandbox[fn] = mn[fn];
- }
- });
- sandbox.runEmulatorCmd = (cmd, cb) => this.runEmulatorCmd(cmd, cb);
- sandbox.runEmulatorShell = (args, cb) => this.runEmulatorShell(args, cb);
+function* execute(script, args, timeout, opts) {
+ opts.timeout = timeout;
+ script = importedScripts.for("content").concat(script);
- sandbox.asyncComplete = (obj, id) => {
- if (id == asyncTestCommandId) {
- curContainer.frame.removeEventListener("unload", onunload, false);
- curContainer.frame.clearTimeout(asyncTestTimeoutId);
-
- if (inactivityTimeoutId != null) {
- curContainer.frame.clearTimeout(inactivityTimeoutId);
- }
-
- sendSyncMessage("Marionette:shareData",
- {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
- marionetteLogObj.clearLogs();
+ let sb = sandbox.createMutable(curContainer.frame);
+ let wargs = elementManager.convertWrappedArguments(args, curContainer);
+ let res = yield evaluate.sandbox(sb, script, wargs, opts);
- if (error.isError(obj)) {
- sendError(obj, id);
- } else {
- if (Object.keys(_emu_cbs).length) {
- _emu_cbs = {};
- sendError(new WebDriverError("Emulator callback still pending when finish() called"), id);
- } else {
- sendResponse(elementManager.wrapValue(obj), id);
- }
- }
-
- asyncTestRunning = false;
- asyncTestTimeoutId = undefined;
- asyncTestCommandId = undefined;
- inactivityTimeoutId = null;
- }
- };
-
- sandbox.finish = function() {
- if (asyncTestRunning) {
- sandbox.asyncComplete(mn.generate_results(), sandbox.asyncTestCommandId);
- } else {
- return mn.generate_results();
- }
- };
- sandbox.marionetteScriptFinished = val =>
- sandbox.asyncComplete(val, sandbox.asyncTestCommandId);
-
- sandboxes[sandboxName] = sandbox;
+ return elementManager.wrapValue(res);
}
-/**
- * Execute the given script either as a function body (executeScript)
- * or directly (for mochitest like JS Marionette tests).
- */
-function executeScript(msg, directInject) {
- // Set up inactivity timeout.
- if (msg.json.inactivityTimeout) {
- let setTimer = function() {
- inactivityTimeoutId = curContainer.frame.setTimeout(function() {
- sendError(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId);
- }, msg.json.inactivityTimeout);
- };
+function* executeInSandbox(script, args, timeout, opts) {
+ opts.timeout = timeout;
+ script = importedScripts.for("content").concat(script);
- setTimer();
- heartbeatCallback = function() {
- curContainer.frame.clearTimeout(inactivityTimeoutId);
- setTimer();
- };
- }
-
- asyncTestCommandId = msg.json.command_id;
- let script = msg.json.script;
- let filename = msg.json.filename;
- sandboxName = msg.json.sandboxName;
-
- if (msg.json.newSandbox ||
- !(sandboxName in sandboxes) ||
- (sandboxes[sandboxName].window != curContainer.frame)) {
- createExecuteContentSandbox(curContainer.frame, msg.json.timeout);
- if (!sandboxes[sandboxName]) {
- sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId);
- return;
- }
- } else {
- sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
+ let sb = sandboxes.get(opts.sandboxName, opts.newSandbox);
+ if (opts.sandboxName) {
+ sb = sandbox.augment(sb, {global: sb});
+ sb = sandbox.augment(sb, new logging.Adapter(contentLog));
+ let emulatorClient = new emulator.EmulatorServiceClient(asyncChrome);
+ sb = sandbox.augment(sb, new emulator.Adapter(emulatorClient));
}
- let sandbox = sandboxes[sandboxName];
+ let wargs = elementManager.convertWrappedArguments(args, curContainer);
+ let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
- try {
- if (directInject) {
- script = importedScripts.for("content").concat(script);
- let res = Cu.evalInSandbox(script, sandbox, "1.8", filename ? filename : "dummy file" ,0);
+ let res = yield evaluatePromise;
+ sendSyncMessage("Marionette:shareData", {log: elementManager.wrapValue(contentLog.get())});
+ return elementManager.wrapValue(res);
+}
- sendSyncMessage("Marionette:shareData",
- {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
- marionetteLogObj.clearLogs();
+function* executeSimpleTest(script, args, timeout, opts) {
+ opts.timeout = timeout;
+ let win = curContainer.frame;
+ script = importedScripts.for("content").concat(script);
- if (res == undefined || res.passed == undefined) {
- sendError(new JavaScriptError("Marionette.finish() not called"), asyncTestCommandId);
- }
- else {
- sendResponse(elementManager.wrapValue(res), asyncTestCommandId);
- }
- }
- else {
- try {
- sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
- msg.json.args, curContainer), sandbox, { wrapReflectors: true });
- } catch (e) {
- sendError(e, asyncTestCommandId);
- return;
- }
+ let harness = new simpletest.Harness(
+ win,
+ "content",
+ contentLog,
+ timeout,
+ marionetteTestName);
+ let sb = sandbox.createSimpleTest(curContainer.frame, harness);
+ // TODO(ato): Not sure this is needed:
+ sb = sandbox.augment(sb, new logging.Adapter(contentLog));
- script = "var __marionetteFunc = function(){" + script + "};" +
- "__marionetteFunc.apply(null, __marionetteParams);";
- script = importedScripts.for("content").concat(script);
+ let wargs = elementManager.convertWrappedArguments(args, curContainer);
+ let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
- let res = Cu.evalInSandbox(script, sandbox, "1.8", filename ? filename : "dummy file", 0);
- sendSyncMessage("Marionette:shareData",
- {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
- marionetteLogObj.clearLogs();
- sendResponse(elementManager.wrapValue(res), asyncTestCommandId);
- }
- } catch (e) {
- let err = new JavaScriptError(
- e,
- "execute_script",
- msg.json.filename,
- msg.json.line,
- script);
- sendError(err, asyncTestCommandId);
- }
+ let res = yield evaluatePromise;
+ sendSyncMessage("Marionette:shareData", {log: elementManager.wrapValue(contentLog.get())});
+ return elementManager.wrapValue(res);
}
/**
* Sets the test name, used in logging messages.
*/
function setTestName(msg) {
marionetteTestName = msg.json.value;
sendOk(msg.json.command_id);
}
/**
- * Execute async script
- */
-function executeAsyncScript(msg) {
- executeWithCallback(msg);
-}
-
-/**
* Receive file objects from chrome in order to complete a
* sendKeysToElement action on a file input element.
*/
function receiveFiles(msg) {
if ('error' in msg.json) {
let err = new InvalidArgumentError(msg.json.error);
sendError(err, msg.json.command_id);
return;
@@ -699,123 +588,16 @@ function receiveFiles(msg) {
let fs = Array.prototype.slice.call(fileInputElement.files);
fs.push(msg.json.file);
fileInputElement.mozSetFileArray(fs);
fileInputElement = null;
sendOk(msg.json.command_id);
}
/**
- * Execute pure JS test. Handles both async and sync cases.
- */
-function executeJSScript(msg) {
- if (msg.json.async) {
- executeWithCallback(msg, msg.json.async);
- }
- else {
- executeScript(msg, true);
- }
-}
-
-/**
- * This function is used by executeAsync and executeJSScript to execute a script
- * in a sandbox.
- *
- * For executeJSScript, it will return a message only when the finish() method is called.
- * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
- * method is called, or if it times out.
- */
-function executeWithCallback(msg, useFinish) {
- // Set up inactivity timeout.
- if (msg.json.inactivityTimeout) {
- let setTimer = function() {
- inactivityTimeoutId = curContainer.frame.setTimeout(function() {
- sandbox.asyncComplete(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId);
- }, msg.json.inactivityTimeout);
- };
-
- setTimer();
- heartbeatCallback = function() {
- curContainer.frame.clearTimeout(inactivityTimeoutId);
- setTimer();
- };
- }
-
- let script = msg.json.script;
- let filename = msg.json.filename;
- asyncTestCommandId = msg.json.command_id;
- sandboxName = msg.json.sandboxName;
-
- onunload = function() {
- sendError(new JavaScriptError("unload was called"), asyncTestCommandId);
- };
- curContainer.frame.addEventListener("unload", onunload, false);
-
- if (msg.json.newSandbox ||
- !(sandboxName in sandboxes) ||
- (sandboxes[sandboxName].window != curContainer.frame)) {
- createExecuteContentSandbox(curContainer.frame, msg.json.timeout);
- if (!sandboxes[sandboxName]) {
- sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId);
- return;
- }
- }
- else {
- sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
- }
- let sandbox = sandboxes[sandboxName];
- sandbox.tag = script;
-
- asyncTestTimeoutId = curContainer.frame.setTimeout(function() {
- sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId);
- }, msg.json.timeout);
-
- originalOnError = curContainer.frame.onerror;
- curContainer.frame.onerror = function errHandler(msg, url, line) {
- sandbox.asyncComplete(new JavaScriptError(msg + "@" + url + ", line " + line), asyncTestCommandId);
- curContainer.frame.onerror = originalOnError;
- };
-
- let scriptSrc;
- if (useFinish) {
- if (msg.json.timeout == null || msg.json.timeout == 0) {
- sendError(new TimeoutError("Please set a timeout"), asyncTestCommandId);
- }
- scriptSrc = script;
- }
- else {
- try {
- sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
- msg.json.args, curContainer), sandbox, { wrapReflectors: true });
- } catch (e) {
- sendError(e, asyncTestCommandId);
- return;
- }
-
- scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
- "var __marionetteFunc = function() { " + script + "};" +
- "__marionetteFunc.apply(null, __marionetteParams); ";
- }
-
- try {
- asyncTestRunning = true;
- scriptSrc = importedScripts.for("content").concat(scriptSrc);
- Cu.evalInSandbox(scriptSrc, sandbox, "1.8", filename ? filename : "dummy file", 0);
- } catch (e) {
- let err = new JavaScriptError(
- e,
- "execute_async_script",
- msg.json.filename,
- msg.json.line,
- scriptSrc);
- sandbox.asyncComplete(err, asyncTestCommandId);
- }
-}
-
-/**
* This function creates a touch event given a touch type and a touch
*/
function emitTouchEvent(type, touch) {
if (!wasInterrupted()) {
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";
dumpLog(loggingInfo);
var docShell = curContainer.frame.document.defaultView.
QueryInterface(Components.interfaces.nsIInterfaceRequestor).
@@ -833,20 +615,20 @@ function emitTouchEvent(type, touch) {
radiusX: touch.radiusX, radiusY: touch.radiusY,
rotation: touch.rotationAngle, force: touch.force });
return;
}
}
// we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
/*
Disabled per bug 888303
- marionetteLogObj.log(loggingInfo, "TRACE");
+ contentLog.log(loggingInfo, "TRACE");
sendSyncMessage("Marionette:shareData",
- {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
- marionetteLogObj.clearLogs();
+ {log: elementManager.wrapValue(contentLog.get())});
+ contentLog.clear();
*/
let domWindowUtils = curContainer.frame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
}
}
/**
* Function that perform a single tap
@@ -1130,17 +912,16 @@ function get(msg) {
// Prevent DOMContentLoaded events from frames from invoking this
// code, unless the event is coming from the frame associated with
// the current window (i.e. someone has used switch_to_frame).
onDOMContentLoaded = function onDOMContentLoaded(event) {
if (!event.originalTarget.defaultView.frameElement ||
event.originalTarget.defaultView.frameElement == curContainer.frame.frameElement) {
pollForReadyState(msg, start, () => {
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
- onDOMContentLoaded = null;
});
}
};
function timerFunc() {
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
sendError(new TimeoutError("Error loading page, timed out (onDOMContentLoaded)"), msg.json.command_id);
}
@@ -1149,17 +930,16 @@ function get(msg) {
}
addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
if (isB2G) {
curContainer.frame.location = msg.json.url;
} else {
// We need to move to the top frame before navigating
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
curContainer.frame = content;
-
curContainer.frame.location = msg.json.url;
}
}
/**
* Cancel the polling and remove the event listener associated with a current
* navigation request in case we're interupted by an onbeforeunload handler
* and navigation doesn't complete.
@@ -1216,17 +996,17 @@ function goForward(msg) {
/**
* Refresh the page
*/
function refresh(msg) {
let command_id = msg.json.command_id;
curContainer.frame.location.reload(true);
let listen = function() {
- removeEventListener("DOMContentLoaded", arguments.callee, false);
+ removeEventListener("DOMContentLoaded", listen, false);
sendOk(command_id);
};
addEventListener("DOMContentLoaded", listen, false);
}
/**
* Find an element in the current browsing context's document using the
* given search strategy.
@@ -1611,29 +1391,30 @@ function switchToFrame(msg) {
if (foundFrame === null) {
sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id);
return true;
}
// send a synchronous message to let the server update the currently active
// frame element (for getActiveFrame)
- let frameValue = elementManager.wrapValue(curContainer.frame.wrappedJSObject)['ELEMENT'];
- sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
+ let frameValue = elementManager.wrapValue(curContainer.frame.wrappedJSObject).ELEMENT;
+ sendSyncMessage("Marionette:switchedToFrame", {frameValue: frameValue});
let rv = null;
if (curContainer.frame.contentWindow === null) {
// The frame we want to switch to is a remote/OOP frame;
// notify our parent to handle the switch
curContainer.frame = content;
rv = {win: parWindow, frame: foundFrame};
} else {
curContainer.frame = curContainer.frame.contentWindow;
- if (msg.json.focus)
+ if (msg.json.focus) {
curContainer.frame.focus();
+ }
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
}
sendResponse(rv, command_id);
}
function addCookie(cookie) {
cookies.add(cookie.name, cookie.value, cookie);
new file mode 100644
--- /dev/null
+++ b/testing/marionette/logging.js
@@ -0,0 +1,75 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["logging"];
+
+this.logging = {};
+
+/** Simple logger that is used in Simple Test harness tests. */
+logging.ContentLogger = class {
+ constructor() {
+ this.logs_ = [];
+ }
+
+ /**
+ * Append a log entry.
+ *
+ * @param {string} message
+ * Log entry message.
+ * @param {string=} level
+ * Severity of entry. Defaults to "INFO".
+ */
+ log(message, level = "INFO") {
+ let now = (new Date()).toString();
+ this.logs_.push([level, message, now]);
+ }
+
+ /**
+ * Append array of log entries.
+ *
+ * @param {Array.<Array<string, string, string>>} messages
+ * List of log entries, that are of the form severity, message,
+ * and date.
+ */
+ addAll(messages) {
+ for (let message of messages) {
+ this.logs_.push(message);
+ }
+ }
+
+ /**
+ * Gets current log entries and clears the cache.
+ *
+ * @return {Array.<Array<string, string, string>>}
+ * Log entries of the form severity, message, and date.
+ */
+ get() {
+ let logs = this.logs_;
+ this.logs_ = [];
+ return logs;
+ }
+};
+
+/**
+ * Adapts an instance of ContentLogger for use in a sandbox. Is consumed
+ * by sandbox.augment.
+ */
+logging.Adapter = class {
+ constructor(logger = null) {
+ this.logger = logger;
+ }
+
+ get exports() {
+ return new Map([["log", this.log.bind(this)]]);
+ }
+
+ log(message, level = "INFO") {
+ dump(`MARIONETTE LOG: ${level}: ${message}\n`);
+ if (this.logger) {
+ this.logger.log(message, level);
+ }
+ }
+};
--- a/testing/marionette/proxy.js
+++ b/testing/marionette/proxy.js
@@ -219,16 +219,20 @@ proxy.AsyncMessageChannel = class {
callback(msg);
};
this.mm.addMessageListener(path, autoRemover);
this.listeners_.set(path, autoRemover);
}
removeListener_(path) {
+ if (!this.listeners_.has(path)) {
+ return true;
+ }
+
let l = this.listeners_.get(path);
this.mm.removeMessageListener(path, l[1]);
return this.listeners_.delete(path);
}
removeAllListeners_() {
let ok = true;
for (let [p, cb] of this.listeners_) {
@@ -239,16 +243,88 @@ proxy.AsyncMessageChannel = class {
};
proxy.AsyncMessageChannel.ReplyType = {
Ok: 0,
Value: 1,
Error: 2,
};
/**
+ * A transparent content-to-chrome RPC interface where responses are
+ * presented as promises.
+ *
+ * @param {nsIFrameMessageManager} frameMessageManager
+ * The content frame's message manager, which itself is usually an
+ * implementor of.
+ */
+proxy.toChromeAsync = function(frameMessageManager) {
+ let sender = new AsyncChromeSender(frameMessageManager);
+ return new Proxy(sender, ownPriorityGetterTrap);
+};
+
+/**
+ * Sends asynchronous RPC messages to chrome space using a frame's
+ * sendAsyncMessage (nsIAsyncMessageSender) function.
+ *
+ * Example on how to use from a frame content script:
+ *
+ * let sender = new AsyncChromeSender(messageManager);
+ * let promise = sender.send("runEmulatorCmd", "my command");
+ * let rv = yield promise;
+ */
+this.AsyncChromeSender = class {
+ constructor(frameMessageManager) {
+ this.mm = frameMessageManager;
+ }
+
+ /**
+ * Call registered function in chrome context.
+ *
+ * @param {string} name
+ * Function to call in the chrome, e.g. for "Marionette:foo", use
+ * "foo".
+ * @param {?} args
+ * Argument list to pass the function. Must be JSON serialisable.
+ *
+ * @return {Promise}
+ * A promise that resolves to the value sent back.
+ */
+ send(name, args) {
+ let uuid = uuidgen.generateUUID().toString();
+
+ let proxy = new Promise((resolve, reject) => {
+ let responseListener = msg => {
+ if (msg.json.id != uuid) {
+ return;
+ }
+
+ this.mm.removeMessageListener(
+ "Marionette:listenerResponse", responseListener);
+
+ if ("value" in msg.json) {
+ resolve(msg.json.value);
+ } else if ("error" in msg.json) {
+ reject(msg.json.error);
+ } else {
+ throw new TypeError(
+ `Unexpected response: ${msg.name} ${JSON.stringify(msg.json)}`);
+ }
+ };
+
+ let msg = {arguments: marshal(args), id: uuid};
+ this.mm.addMessageListener(
+ "Marionette:listenerResponse", responseListener);
+ this.mm.sendAsyncMessage("Marionette:" + name, msg);
+ });
+
+ return proxy;
+ }
+};
+
+/**
* Creates a transparent interface from the content- to the chrome context.
*
* Calls to this object will be proxied via the frame's sendSyncMessage
* (nsISyncMessageSender) function. Since the message is synchronous,
* the return value is presented as a return value.
*
* Example on how to use from a frame content script:
*
@@ -260,30 +336,31 @@ proxy.AsyncMessageChannel.ReplyType = {
*/
proxy.toChrome = function(sendSyncMessageFn) {
let sender = new proxy.SyncChromeSender(sendSyncMessageFn);
return new Proxy(sender, ownPriorityGetterTrap);
};
/**
* The SyncChromeSender sends synchronous RPC messages to the chrome
- * context, using a frame's sendSyncMessage (nsISyncMessageSender) function.
+ * context, using a frame's sendSyncMessage (nsISyncMessageSender)
+ * function.
*
* Example on how to use from a frame content script:
*
* let sender = new SyncChromeSender(sendSyncMessage.bind(this));
* let res = sender.send("addCookie", cookie);
*/
proxy.SyncChromeSender = class {
constructor(sendSyncMessage) {
this.sendSyncMessage_ = sendSyncMessage;
}
send(func, args) {
- let name = "Marionette:" + func;
+ let name = "Marionette:" + func.toString();
return this.sendSyncMessage_(name, marshal(args));
}
};
var marshal = function(args) {
if (args.length == 1 && typeof args[0] == "object") {
return args[0];
}
--- a/testing/marionette/simpletest.js
+++ b/testing/marionette/simpletest.js
@@ -1,110 +1,114 @@
/* 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/. */
-var {utils: Cu} = Components;
+"use strict";
+
+const {utils: Cu} = Components;
Cu.import("chrome://marionette/content/error.js");
-this.EXPORTED_SYMBOLS = ["Marionette"];
+this.EXPORTED_SYMBOLS = ["simpletest"];
-/*
- * The Marionette object, passed to the script context.
+this.simpletest = {};
+
+/**
+ * The simpletest harness, exposed in the script evaluation sandbox.
*/
-this.Marionette = function(window, context, logObj, timeout,
- heartbeatCallback, testName) {
- this.window = window;
- this.tests = [];
- this.logObj = logObj;
- this.context = context;
- this.timeout = timeout;
- this.heartbeatCallback = heartbeatCallback;
- this.testName = testName;
- this.TEST_UNEXPECTED_FAIL = "TEST-UNEXPECTED-FAIL";
- this.TEST_UNEXPECTED_PASS = "TEST-UNEXPECTED-PASS";
- this.TEST_PASS = "TEST-PASS";
- this.TEST_KNOWN_FAIL = "TEST-KNOWN-FAIL";
-};
+simpletest.Harness = class {
+ constructor(window, context, contentLogger, timeout, testName) {
+ this.window = window;
+ this.tests = [];
+ this.logger = contentLogger;
+ this.context = context;
+ this.timeout = timeout;
+ this.testName = testName;
+ this.TEST_UNEXPECTED_FAIL = "TEST-UNEXPECTED-FAIL";
+ this.TEST_UNEXPECTED_PASS = "TEST-UNEXPECTED-PASS";
+ this.TEST_PASS = "TEST-PASS";
+ this.TEST_KNOWN_FAIL = "TEST-KNOWN-FAIL";
+ }
-Marionette.prototype = {
- exports: [
- "ok",
- "is",
- "isnot",
- "todo",
- "log",
- "getLogs",
- "generate_results",
- "waitFor",
- "TEST_PASS",
- "TEST_KNOWN_FAIL",
- "TEST_UNEXPECTED_FAIL",
- "TEST_UNEXPECTED_PASS"
- ],
+ get exports() {
+ return new Map([
+ ["ok", this.ok.bind(this)],
+ ["is", this.is.bind(this)],
+ ["isnot", this.isnot.bind(this)],
+ ["todo", this.todo.bind(this)],
+ ["log", this.log.bind(this)],
+ ["getLogs", this.getLogs.bind(this)],
+ ["generate_results", this.generate_results.bind(this)],
+ ["waitFor", this.waitFor.bind(this)],
+ ["TEST_PASS", this.TEST_PASS],
+ ["TEST_KNOWN_FAIL", this.TEST_KNOWN_FAIL],
+ ["TEST_UNEXPECTED_FAIL", this.TEST_UNEXPECTED_FAIL],
+ ["TEST_UNEXPECTED_PASS", this.TEST_UNEXPECTED_PASS],
+ ]);
+ }
- addTest: function Marionette__addTest(condition, name, passString, failString, diag, state) {
- let test = {'result': !!condition, 'name': name, 'diag': diag, 'state': state};
- this.logResult(test,
- typeof(passString) == "undefined" ? this.TEST_PASS : passString,
- typeof(failString) == "undefined" ? this.TEST_UNEXPECTED_FAIL : failString);
+ addTest(condition, name, passString, failString, diag, state) {
+ let test = {
+ result: !!condition,
+ name: name,
+ diag: diag,
+ state: state
+ };
+ this.logResult(
+ test,
+ typeof passString == "undefined" ? this.TEST_PASS : passString,
+ typeof failString == "undefined" ? this.TEST_UNEXPECTED_FAIL : failString);
this.tests.push(test);
- },
+ }
- ok: function Marionette__ok(condition, name, passString, failString) {
- this.heartbeatCallback();
- let diag = this.repr(condition) + " was " + !!condition + ", expected true";
+ ok(condition, name, passString, failString) {
+ let diag = `${this.repr(condition)} was ${!!condition}, expected true`;
this.addTest(condition, name, passString, failString, diag);
- },
+ }
- is: function Marionette__is(a, b, name, passString, failString) {
- this.heartbeatCallback();
+ is(a, b, name, passString, failString) {
let pass = (a == b);
let diag = pass ? this.repr(a) + " should equal " + this.repr(b)
: "got " + this.repr(a) + ", expected " + this.repr(b);
this.addTest(pass, name, passString, failString, diag);
- },
+ }
- isnot: function Marionette__isnot (a, b, name, passString, failString) {
- this.heartbeatCallback();
+ isnot(a, b, name, passString, failString) {
let pass = (a != b);
let diag = pass ? this.repr(a) + " should not equal " + this.repr(b)
: "didn't expect " + this.repr(a) + ", but got it";
this.addTest(pass, name, passString, failString, diag);
- },
+ }
- todo: function Marionette__todo(condition, name, passString, failString) {
- this.heartbeatCallback();
+ todo(condition, name, passString, failString) {
let diag = this.repr(condition) + " was expected false";
this.addTest(!condition,
name,
typeof(passString) == "undefined" ? this.TEST_KNOWN_FAIL : passString,
typeof(failString) == "undefined" ? this.TEST_UNEXPECTED_FAIL : failString,
diag,
"todo");
- },
+ }
- log: function Marionette__log(msg, level) {
- this.heartbeatCallback();
+ log(msg, level) {
dump("MARIONETTE LOG: " + (level ? level : "INFO") + ": " + msg + "\n");
- if (this.logObj != null) {
- this.logObj.log(msg, level);
+ if (this.logger) {
+ this.logger.log(msg, level);
}
- },
+ }
- getLogs: function Marionette__getLogs() {
- this.heartbeatCallback();
- if (this.logObj != null) {
- this.logObj.getLogs();
+ // TODO(ato): Suspect this isn't used anywhere
+ getLogs() {
+ if (this.logger) {
+ return this.logger.get();
}
- },
+ }
- generate_results: function Marionette__generate_results() {
- this.heartbeatCallback();
+ generate_results() {
let passed = 0;
let failures = [];
let expectedFailures = [];
let unexpectedSuccesses = [];
for (let i in this.tests) {
let isTodo = (this.tests[i].state == "todo");
if(this.tests[i].result) {
if (isTodo) {
@@ -120,83 +124,85 @@ Marionette.prototype = {
}
else {
failures.push({'name': this.tests[i].name, 'diag': this.tests[i].diag});
}
}
}
// Reset state in case this object is reused for more tests.
this.tests = [];
- return {"passed": passed, "failures": failures, "expectedFailures": expectedFailures,
- "unexpectedSuccesses": unexpectedSuccesses};
- },
+ return {
+ passed: passed,
+ failures: failures,
+ expectedFailures: expectedFailures,
+ unexpectedSuccesses: unexpectedSuccesses,
+ };
+ }
- logToFile: function Marionette__logToFile(file) {
- this.heartbeatCallback();
+ logToFile(file) {
//TODO
- },
+ }
- logResult: function Marionette__logResult(test, passString, failString) {
- this.heartbeatCallback();
+ logResult(test, passString, failString) {
//TODO: dump to file
let resultString = test.result ? passString : failString;
let diagnostic = test.name + (test.diag ? " - " + test.diag : "");
let msg = resultString + " | " + this.testName + " | " + diagnostic;
dump("MARIONETTE TEST RESULT:" + msg + "\n");
- },
+ }
+
+ repr(o) {
+ if (typeof o == "undefined") {
+ return "undefined";
+ } else if (o === null) {
+ return "null";
+ }
- repr: function Marionette__repr(o) {
- if (typeof(o) == "undefined") {
- return "undefined";
- } else if (o === null) {
- return "null";
- }
- try {
- if (typeof(o.__repr__) == 'function') {
- return o.__repr__();
- } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
- return o.repr();
- }
- } catch (e) {
- }
- try {
- if (typeof(o.NAME) == 'string' && (
- o.toString == Function.prototype.toString ||
- o.toString == Object.prototype.toString
- )) {
- return o.NAME;
- }
- } catch (e) {
+ try {
+ if (typeof o.__repr__ == "function") {
+ return o.__repr__();
+ } else if (typeof o.repr == "function" && o.repr !== arguments.callee) {
+ return o.repr();
+ }
+ } catch (e) {}
+
+ try {
+ if (typeof o.NAME === "string" &&
+ (o.toString === Function.prototype.toString || o.toString === Object.prototype.toString)) {
+ return o.NAME;
}
- let ostring;
- try {
- ostring = (o + "");
- } catch (e) {
- return "[" + typeof(o) + "]";
+ } catch (e) {}
+
+ let ostring;
+ try {
+ ostring = (o + "");
+ } catch (e) {
+ return "[" + typeof(o) + "]";
+ }
+
+ if (typeof o == "function") {
+ o = ostring.replace(/^\s+/, "");
+ let idx = o.indexOf("{");
+ if (idx != -1) {
+ o = o.substr(0, idx) + "{...}";
}
- if (typeof(o) == "function") {
- o = ostring.replace(/^\s+/, "");
- let idx = o.indexOf("{");
- if (idx != -1) {
- o = o.substr(0, idx) + "{...}";
- }
- }
- return ostring;
- },
+ }
+ return ostring;
+ }
- waitFor: function test_waitFor(callback, test, timeout) {
- this.heartbeatCallback();
- if (test()) {
- callback();
- return;
- }
- var now = new Date();
- var deadline = (timeout instanceof Date) ? timeout :
- new Date(now.valueOf() + (typeof(timeout) == "undefined" ? this.timeout : timeout))
- if (deadline <= now) {
- dump("waitFor timeout: " + test.toString() + "\n");
- // the script will timeout here, so no need to raise a separate
- // timeout exception
- return;
- }
- this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, deadline);
- },
+ waitFor(callback, test, timeout) {
+ if (test()) {
+ callback();
+ return;
+ }
+
+ let now = new Date();
+ let deadline = (timeout instanceof Date) ? timeout :
+ new Date(now.valueOf() + (typeof timeout == "undefined" ? this.timeout : timeout));
+ if (deadline <= now) {
+ dump("waitFor timeout: " + test.toString() + "\n");
+ // the script will timeout here, so no need to raise a separate
+ // timeout exception
+ return;
+ }
+ this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, deadline);
+ }
};