mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Backed out changeset c2aa06a2ab49 (bug 1211489) for Gij(39) failures. r=backout
This commit is contained in:
parent
86b25d9035
commit
e267a3bf1b
163
testing/marionette/command.js
Normal file
163
testing/marionette/command.js
Normal file
@ -0,0 +1,163 @@
|
||||
/* 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 {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["CommandProcessor", "Response"];
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
const validator = {
|
||||
exclusionary: {
|
||||
"capabilities": ["error", "value"],
|
||||
"error": ["value", "sessionId", "capabilities"],
|
||||
"sessionId": ["error", "value"],
|
||||
"value": ["error", "sessionId", "capabilities"],
|
||||
},
|
||||
|
||||
set: function(obj, prop, val) {
|
||||
let tests = this.exclusionary[prop];
|
||||
if (tests) {
|
||||
for (let t of tests) {
|
||||
if (obj.hasOwnProperty(t)) {
|
||||
throw new TypeError(`${t} set, cannot set ${prop}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj[prop] = val;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The response body is exposed as an argument to commands.
|
||||
* Commands can set fields on the body through defining properties.
|
||||
*
|
||||
* Setting properties invokes a validator that performs tests for
|
||||
* mutually exclusionary fields on the input against the existing data
|
||||
* in the body.
|
||||
*
|
||||
* For example setting the {@code error} property on the body when
|
||||
* {@code value}, {@code sessionId}, or {@code capabilities} have been
|
||||
* set previously will cause an error.
|
||||
*/
|
||||
this.ResponseBody = () => new Proxy({}, validator);
|
||||
|
||||
/**
|
||||
* Represents the response returned from the remote end after execution
|
||||
* of its corresponding command.
|
||||
*
|
||||
* The response is a mutable object passed to each command for
|
||||
* modification through the available setters. To send data in a response,
|
||||
* you modify the body property on the response. The body property can
|
||||
* also be replaced completely.
|
||||
*
|
||||
* The response is sent implicitly by CommandProcessor when a command
|
||||
* has finished executing, and any modifications made subsequent to that
|
||||
* will have no effect.
|
||||
*
|
||||
* @param {number} cmdId
|
||||
* UUID tied to the corresponding command request this is
|
||||
* a response for.
|
||||
* @param {function(Object, number)} respHandler
|
||||
* Callback function called on responses.
|
||||
*/
|
||||
this.Response = function(cmdId, respHandler) {
|
||||
this.id = cmdId;
|
||||
this.respHandler = respHandler;
|
||||
this.sent = false;
|
||||
this.body = ResponseBody();
|
||||
};
|
||||
|
||||
Response.prototype.send = function() {
|
||||
if (this.sent) {
|
||||
throw new RangeError("Response has already been sent: " + this.toString());
|
||||
}
|
||||
this.respHandler(this.body, this.id);
|
||||
this.sent = true;
|
||||
};
|
||||
|
||||
Response.prototype.sendError = function(err) {
|
||||
let wd = error.isWebDriverError(err);
|
||||
let we = wd ? err : new WebDriverError(err.message);
|
||||
|
||||
this.body.error = we.status;
|
||||
this.body.message = we.message || null;
|
||||
this.body.stacktrace = we.stack || null;
|
||||
|
||||
this.send();
|
||||
|
||||
// propagate errors that are implementation problems
|
||||
if (!wd) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The command processor receives messages on execute(payload, …)
|
||||
* from the dispatcher, processes them, and wraps the functions that
|
||||
* it executes from the WebDriver implementation, driver.
|
||||
*
|
||||
* @param {GeckoDriver} driver
|
||||
* Reference to the driver implementation.
|
||||
*/
|
||||
this.CommandProcessor = function(driver) {
|
||||
this.driver = driver;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a WebDriver command based on the received payload,
|
||||
* which is expected to be an object with a "parameters" property
|
||||
* that is a simple key/value collection of arguments.
|
||||
*
|
||||
* The respHandler function will be called with the JSON object to
|
||||
* send back to the client.
|
||||
*
|
||||
* The cmdId is the UUID tied to this request that prevents
|
||||
* the dispatcher from sending responses in the wrong order.
|
||||
*
|
||||
* @param {Object} payload
|
||||
* Message as received from client.
|
||||
* @param {function(Object, number)} respHandler
|
||||
* Callback function called on responses.
|
||||
* @param {number} cmdId
|
||||
* The unique identifier for the command to execute.
|
||||
*/
|
||||
CommandProcessor.prototype.execute = function(payload, respHandler, cmdId) {
|
||||
let cmd = payload;
|
||||
let resp = new Response(cmdId, respHandler);
|
||||
let sendResponse = resp.send.bind(resp);
|
||||
let sendError = resp.sendError.bind(resp);
|
||||
|
||||
// Ideally handlers shouldn't have to care about the command ID,
|
||||
// but some methods (newSession, executeScript, et al.) have not
|
||||
// yet been converted to use the new form of request dispatching.
|
||||
cmd.id = cmdId;
|
||||
|
||||
let req = Task.spawn(function*() {
|
||||
let fn = this.driver.commands[cmd.name];
|
||||
if (typeof fn == "undefined") {
|
||||
throw new UnknownCommandError(cmd.name);
|
||||
}
|
||||
|
||||
let rv = yield fn.bind(this.driver)(cmd, resp);
|
||||
|
||||
if (typeof rv != "undefined") {
|
||||
if (typeof rv != "object") {
|
||||
resp.body = {value: rv};
|
||||
} else {
|
||||
resp.body = rv;
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
req.then(sendResponse, sendError).catch(error.report);
|
||||
};
|
@ -4,21 +4,23 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/driver.js");
|
||||
Cu.import("chrome://marionette/content/command.js");
|
||||
Cu.import("chrome://marionette/content/emulator.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/message.js");
|
||||
Cu.import("chrome://marionette/content/driver.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Dispatcher"];
|
||||
|
||||
const PROTOCOL_VERSION = 3;
|
||||
const PROTOCOL_VERSION = 2;
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
/**
|
||||
* Manages a Marionette connection, and dispatches packets received to
|
||||
@ -31,109 +33,111 @@ const logger = Log.repository.getLogger("Marionette");
|
||||
* @param {function(Emulator): GeckoDriver} driverFactory
|
||||
* A factory function that takes an Emulator as argument and produces
|
||||
* a GeckoDriver.
|
||||
* @param {function()} stopSignal
|
||||
* Signal to stop the Marionette server.
|
||||
*/
|
||||
this.Dispatcher = function(connId, transport, driverFactory) {
|
||||
this.connId = connId;
|
||||
this.Dispatcher = function(connId, transport, driverFactory, stopSignal) {
|
||||
this.id = 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;
|
||||
// transport hooks are Dispatcher.prototype.onPacket
|
||||
// and Dispatcher.prototype.onClosed
|
||||
this.conn.hooks = this;
|
||||
|
||||
this.emulator = new Emulator(this.sendEmulator.bind(this));
|
||||
this.emulator = new Emulator(msg => this.send(msg, -1));
|
||||
this.driver = driverFactory(this.emulator);
|
||||
this.commandProcessor = new CommandProcessor(this.driver);
|
||||
|
||||
// lookup of commands sent by server to client by message ID
|
||||
this.commands_ = new Map();
|
||||
this.stopSignal_ = stopSignal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Debugger transport callback that dispatches the request.
|
||||
* Request handlers defined in this.requests take presedence
|
||||
* over those defined in this.driver.commands.
|
||||
*/
|
||||
Dispatcher.prototype.onPacket = function(packet) {
|
||||
if (logger.level <= Log.Level.Debug) {
|
||||
logger.debug(this.id + " -> " + JSON.stringify(packet));
|
||||
}
|
||||
|
||||
if (this.requests && this.requests[packet.name]) {
|
||||
this.requests[packet.name].bind(this)(packet);
|
||||
} else {
|
||||
let id = this.beginNewCommand();
|
||||
let send = this.send.bind(this);
|
||||
this.commandProcessor.execute(packet, send, id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Debugger transport callback that cleans up
|
||||
* after a connection is closed.
|
||||
*/
|
||||
Dispatcher.prototype.onClosed = function(reason) {
|
||||
Dispatcher.prototype.onClosed = function(status) {
|
||||
this.driver.sessionTearDown();
|
||||
if (this.onclose) {
|
||||
this.onclose(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback that receives data packets from the client.
|
||||
*
|
||||
* If the message is a Response, we look up the command previously issued
|
||||
* to the client and run its callback, if any. In case of a Command,
|
||||
* the corresponding is executed.
|
||||
*
|
||||
* @param {Array.<number, number, ?, ?>} data
|
||||
* A four element array where the elements, in sequence, signifies
|
||||
* message type, message ID, method name or error, and parameters
|
||||
* or result.
|
||||
*/
|
||||
Dispatcher.prototype.onPacket = function(data) {
|
||||
let msg = Message.fromMsg(data);
|
||||
msg.origin = MessageOrigin.Client;
|
||||
this.log_(msg);
|
||||
// Dispatcher specific command handlers:
|
||||
|
||||
if (msg instanceof Response) {
|
||||
let cmd = this.commands_.get(msg.id);
|
||||
this.commands_.delete(msg.id);
|
||||
cmd.onresponse(msg);
|
||||
} else if (msg instanceof Command) {
|
||||
this.lastId = msg.id;
|
||||
this.execute(msg);
|
||||
Dispatcher.prototype.emulatorCmdResult = function(msg) {
|
||||
switch (this.driver.context) {
|
||||
case Context.CONTENT:
|
||||
this.driver.sendAsync("emulatorCmdResult", msg);
|
||||
break;
|
||||
case Context.CHROME:
|
||||
let cb = this.emulator.popCallback(msg.id);
|
||||
if (!cb) {
|
||||
return;
|
||||
}
|
||||
cb.result(msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a WebDriver command and sends back a response when it has
|
||||
* finished executing.
|
||||
*
|
||||
* Commands implemented in GeckoDriver and registered in its
|
||||
* {@code GeckoDriver.commands} attribute. The return values from
|
||||
* commands are expected to be Promises. If the resolved value of said
|
||||
* promise is not an object, the response body will be wrapped in an object
|
||||
* under a "value" field.
|
||||
*
|
||||
* If the command implementation sends the response itself by calling
|
||||
* {@code resp.send()}, the response is guaranteed to not be sent twice.
|
||||
*
|
||||
* Errors thrown in commands are marshaled and sent back, and if they
|
||||
* are not WebDriverError instances, they are additionally propagated and
|
||||
* reported to {@code Components.utils.reportError}.
|
||||
*
|
||||
* @param {Command} cmd
|
||||
* The requested command to execute.
|
||||
* Quits Firefox with the provided flags and tears down the current
|
||||
* session.
|
||||
*/
|
||||
Dispatcher.prototype.execute = function(cmd) {
|
||||
let resp = new Response(cmd.id, this.send.bind(this));
|
||||
let sendResponse = () => resp.sendConditionally(resp => !resp.sent);
|
||||
let sendError = resp.sendError.bind(resp);
|
||||
Dispatcher.prototype.quitApplication = function(msg) {
|
||||
let id = this.beginNewCommand();
|
||||
|
||||
let req = Task.spawn(function*() {
|
||||
let fn = this.driver.commands[cmd.name];
|
||||
if (typeof fn == "undefined") {
|
||||
throw new UnknownCommandError(cmd.name);
|
||||
}
|
||||
if (this.driver.appName != "Firefox") {
|
||||
this.sendError(new WebDriverError("In app initiated quit only supported in Firefox"));
|
||||
return;
|
||||
}
|
||||
|
||||
let rv = yield fn.bind(this.driver)(cmd, resp);
|
||||
let flags = Ci.nsIAppStartup.eAttemptQuit;
|
||||
for (let k of msg.parameters.flags) {
|
||||
flags |= Ci.nsIAppStartup[k];
|
||||
}
|
||||
|
||||
if (typeof rv != "undefined") {
|
||||
if (typeof rv != "object") {
|
||||
resp.body = {value: rv};
|
||||
} else {
|
||||
resp.body = rv;
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
this.stopSignal_();
|
||||
this.sendOk(id);
|
||||
|
||||
req.then(sendResponse, sendError).catch(error.report);
|
||||
this.driver.sessionTearDown();
|
||||
Services.startup.quit(flags);
|
||||
};
|
||||
|
||||
// Convenience methods:
|
||||
|
||||
Dispatcher.prototype.sayHello = function() {
|
||||
let id = this.beginNewCommand();
|
||||
let whatHo = {
|
||||
applicationType: "gecko",
|
||||
marionetteProtocol: PROTOCOL_VERSION,
|
||||
};
|
||||
this.send(whatHo, id);
|
||||
};
|
||||
|
||||
Dispatcher.prototype.sendOk = function(cmdId) {
|
||||
this.send({}, cmdId);
|
||||
};
|
||||
|
||||
Dispatcher.prototype.sendError = function(err, cmdId) {
|
||||
@ -141,30 +145,6 @@ Dispatcher.prototype.sendError = function(err, cmdId) {
|
||||
resp.sendError(err);
|
||||
};
|
||||
|
||||
// Convenience methods:
|
||||
|
||||
/**
|
||||
* When a client connects we send across a JSON Object defining the
|
||||
* protocol level.
|
||||
*
|
||||
* This is the only message sent by Marionette that does not follow
|
||||
* the regular message format.
|
||||
*/
|
||||
Dispatcher.prototype.sayHello = function() {
|
||||
let whatHo = {
|
||||
applicationType: "gecko",
|
||||
marionetteProtocol: PROTOCOL_VERSION,
|
||||
};
|
||||
this.sendRaw(whatHo);
|
||||
};
|
||||
|
||||
Dispatcher.prototype.sendEmulator = function(name, params, resCb, errCb) {
|
||||
let cmd = new Command(++this.lastId, name, params);
|
||||
cmd.onresult = resCb;
|
||||
cmd.onerror = errCb;
|
||||
this.send(cmd);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delegates message to client or emulator based on the provided
|
||||
* {@code cmdId}. The message is sent over the debugger transport socket.
|
||||
@ -176,69 +156,83 @@ Dispatcher.prototype.sendEmulator = function(name, params, resCb, errCb) {
|
||||
* correct order, emulator callbacks are more transparent and can be sent
|
||||
* at any time. These callbacks won't change the current command state.
|
||||
*
|
||||
* @param {Command,Response} msg
|
||||
* The command or response to send.
|
||||
* @param {Object} payload
|
||||
* The payload to send.
|
||||
* @param {UUID} cmdId
|
||||
* The unique identifier for this payload. {@code -1} signifies
|
||||
* that it's an emulator callback.
|
||||
*/
|
||||
Dispatcher.prototype.send = function(msg) {
|
||||
msg.origin = MessageOrigin.Server;
|
||||
if (msg instanceof Command) {
|
||||
this.commands_.set(msg.id, msg);
|
||||
this.sendToEmulator(msg);
|
||||
} else if (msg instanceof Response) {
|
||||
this.sendToClient(msg);
|
||||
Dispatcher.prototype.send = function(payload, cmdId) {
|
||||
if (emulator.isCallback(cmdId)) {
|
||||
this.sendToEmulator(payload);
|
||||
} else {
|
||||
this.sendToClient(payload, cmdId);
|
||||
this.commandId = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Low-level methods:
|
||||
|
||||
/**
|
||||
* Send command to emulator over the debugger transport socket.
|
||||
*
|
||||
* @param {Command} cmd
|
||||
* The command to issue to the emulator.
|
||||
* Send message to emulator over the debugger transport socket.
|
||||
* Notably this skips out-of-sync command checks.
|
||||
*/
|
||||
Dispatcher.prototype.sendToEmulator = function(cmd) {
|
||||
this.sendMessage(cmd);
|
||||
Dispatcher.prototype.sendToEmulator = function(payload) {
|
||||
this.sendRaw("emulator", payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send given response to the client over the debugger transport socket.
|
||||
* Send given payload as-is to the connected client over the debugger
|
||||
* transport socket.
|
||||
*
|
||||
* @param {Response} resp
|
||||
* The response to send back to the client.
|
||||
* If {@code cmdId} evaluates to false, the current command state isn't
|
||||
* set, or the response is out-of-sync, a warning is logged and this
|
||||
* routine will return (no-op).
|
||||
*/
|
||||
Dispatcher.prototype.sendToClient = function(resp) {
|
||||
Dispatcher.prototype.sendToClient = function(payload, cmdId) {
|
||||
if (!cmdId) {
|
||||
logger.warn("Got response with no command ID");
|
||||
return;
|
||||
} else if (this.commandId === null) {
|
||||
logger.warn(`No current command, ignoring response: ${payload.toSource}`);
|
||||
return;
|
||||
} else if (this.isOutOfSync(cmdId)) {
|
||||
logger.warn(`Ignoring out-of-sync response with command ID: ${cmdId}`);
|
||||
return;
|
||||
}
|
||||
this.driver.responseCompleted();
|
||||
this.sendMessage(resp);
|
||||
this.sendRaw("client", payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Marshal message to the Marionette message format and send it.
|
||||
*
|
||||
* @param {Command,Response} msg
|
||||
* The message to send.
|
||||
* Sends payload as-is over debugger transport socket to client,
|
||||
* and logs it.
|
||||
*/
|
||||
Dispatcher.prototype.sendMessage = function(msg) {
|
||||
this.log_(msg);
|
||||
let payload = msg.toMsg();
|
||||
this.sendRaw(payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the given payload over the debugger transport socket to the
|
||||
* connected client.
|
||||
*
|
||||
* @param {Object} payload
|
||||
* The payload to ship.
|
||||
*/
|
||||
Dispatcher.prototype.sendRaw = function(payload) {
|
||||
Dispatcher.prototype.sendRaw = function(dest, payload) {
|
||||
if (logger.level <= Log.Level.Debug) {
|
||||
logger.debug(this.id + " " + dest + " <- " + JSON.stringify(payload));
|
||||
}
|
||||
this.conn.send(payload);
|
||||
};
|
||||
|
||||
Dispatcher.prototype.log_ = function(msg) {
|
||||
if (logger.level > Log.Level.Debug) {
|
||||
return;
|
||||
}
|
||||
let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
|
||||
logger.debug(this.connId + a + msg);
|
||||
/**
|
||||
* Begins a new command by generating a unique identifier and assigning
|
||||
* it to the current command state {@code Dispatcher.prototype.commandId}.
|
||||
*
|
||||
* @return {UUID}
|
||||
* The generated unique identifier for the current command.
|
||||
*/
|
||||
Dispatcher.prototype.beginNewCommand = function() {
|
||||
let uuid = uuidGen.generateUUID().toString();
|
||||
this.commandId = uuid;
|
||||
return uuid;
|
||||
};
|
||||
|
||||
Dispatcher.prototype.isOutOfSync = function(cmdId) {
|
||||
return this.commandId !== cmdId;
|
||||
};
|
||||
|
||||
Dispatcher.prototype.requests = {
|
||||
emulatorCmdResult: Dispatcher.prototype.emulatorCmdResult,
|
||||
quitApplication: Dispatcher.prototype.quitApplication
|
||||
};
|
||||
|
@ -24,6 +24,7 @@ XPCOMUtils.defineLazyServiceGetter(
|
||||
|
||||
Cu.import("chrome://marionette/content/actions.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/emulator.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/modal.js");
|
||||
Cu.import("chrome://marionette/content/proxy.js");
|
||||
@ -94,17 +95,12 @@ this.Context.fromString = function(s) {
|
||||
* 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.
|
||||
*/
|
||||
this.GeckoDriver = function(appName, device, stopSignal, emulator) {
|
||||
this.GeckoDriver = function(appName, device, emulator) {
|
||||
this.appName = appName;
|
||||
this.stopSignal_ = stopSignal;
|
||||
this.emulator = emulator;
|
||||
// TODO(ato): hack
|
||||
this.emulator.sendToListener = this.sendAsync.bind(this);
|
||||
|
||||
this.sessionId = null;
|
||||
// holds list of BrowserObjs
|
||||
@ -168,7 +164,6 @@ this.GeckoDriver = function(appName, device, stopSignal, emulator) {
|
||||
this.mm = globalMessageManager;
|
||||
this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
|
||||
|
||||
// always keep weak reference to current dialogue
|
||||
this.dialog = null;
|
||||
let handleDialog = (subject, topic) => {
|
||||
let winr;
|
||||
@ -1088,6 +1083,11 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
|
||||
|
||||
let res = yield new Promise(function(resolve, reject) {
|
||||
let chromeAsyncReturnFunc = function(val) {
|
||||
if (that.emulator.cbs.length > 0) {
|
||||
that.emulator.cbs = [];
|
||||
throw new WebDriverError("Emulator callback still pending when finish() called");
|
||||
}
|
||||
|
||||
if (cmd.id == that.sandboxes[sandboxName].command_id) {
|
||||
if (that.timer !== null) {
|
||||
that.timer.cancel();
|
||||
@ -1133,11 +1133,20 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
|
||||
}
|
||||
|
||||
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.sandboxes[sandboxName].runEmulatorCmd = (cmd, cb) => {
|
||||
let ecb = new EmulatorCallback();
|
||||
ecb.onresult = cb;
|
||||
ecb.onerror = chromeAsyncError;
|
||||
this.emulator.pushCallback(ecb);
|
||||
this.emulator.send({emulator_cmd: cmd, id: ecb.id});
|
||||
};
|
||||
this.sandboxes[sandboxName].runEmulatorShell = (args, cb) => {
|
||||
let ecb = new EmulatorCallback();
|
||||
ecb.onresult = cb;
|
||||
ecb.onerror = chromeAsyncError;
|
||||
this.emulator.pushCallback(ecb);
|
||||
this.emulator.send({emulator_shell: args, id: ecb.id});
|
||||
};
|
||||
this.applyArgumentsToSandbox(win, this.sandboxes[sandboxName], args);
|
||||
|
||||
// NB: win.onerror is not hooked by default due to the inability to
|
||||
@ -2791,27 +2800,6 @@ GeckoDriver.prototype.sendKeysToDialog = function(cmd, resp) {
|
||||
true /* ignore visibility check */);
|
||||
};
|
||||
|
||||
/**
|
||||
* Quits Firefox with the provided flags and tears down the current
|
||||
* session.
|
||||
*/
|
||||
GeckoDriver.prototype.quitApplication = function(cmd, resp) {
|
||||
if (this.appName != "Firefox") {
|
||||
throw new WebDriverError("In app initiated quit only supported in Firefox");
|
||||
}
|
||||
|
||||
let flags = Ci.nsIAppStartup.eAttemptQuit;
|
||||
for (let k of cmd.parameters.flags) {
|
||||
flags |= Ci.nsIAppStartup[k];
|
||||
}
|
||||
|
||||
this.stopSignal_();
|
||||
resp.send();
|
||||
|
||||
this.sessionTearDown();
|
||||
Services.startup.quit(flags);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to convert an outerWindowID into a UID that Marionette
|
||||
* tracks.
|
||||
@ -2847,6 +2835,11 @@ GeckoDriver.prototype.receiveMessage = function(message) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "Marionette:runEmulatorCmd":
|
||||
case "Marionette:runEmulatorShell":
|
||||
this.emulator.send(message.json);
|
||||
break;
|
||||
|
||||
case "Marionette:switchToModalOrigin":
|
||||
this.curBrowser.frameManager.switchToModalOrigin(message);
|
||||
this.mm = this.curBrowser.frameManager
|
||||
@ -3029,8 +3022,7 @@ GeckoDriver.prototype.commands = {
|
||||
"dismissDialog": GeckoDriver.prototype.dismissDialog,
|
||||
"acceptDialog": GeckoDriver.prototype.acceptDialog,
|
||||
"getTextFromDialog": GeckoDriver.prototype.getTextFromDialog,
|
||||
"sendKeysToDialog": GeckoDriver.prototype.sendKeysToDialog,
|
||||
"quitApplication": GeckoDriver.prototype.quitApplication,
|
||||
"sendKeysToDialog": GeckoDriver.prototype.sendKeysToDialog
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -4,14 +4,18 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
var {classes: Cc, interfaces: Ci} = Components;
|
||||
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
this.EXPORTED_SYMBOLS = ["emulator", "Emulator", "EmulatorCallback"];
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
this.emulator = {};
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Emulator"];
|
||||
/**
|
||||
* Determines if command ID is an emulator callback.
|
||||
*/
|
||||
this.emulator.isCallback = function(cmdId) {
|
||||
return cmdId < 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the connection between Marionette and the emulator it's
|
||||
@ -22,90 +26,97 @@ this.EXPORTED_SYMBOLS = ["Emulator"];
|
||||
* which is stored in cbs. They are later retreived by their unique ID
|
||||
* using popCallback.
|
||||
*
|
||||
* @param {function(Object)} sendToEmulatorFn
|
||||
* @param {function(Object)} sendFn
|
||||
* Callback function that sends a message to the emulator.
|
||||
* @param {function(Object)} sendToEmulatorFn
|
||||
* Callback function that sends a message asynchronously to the
|
||||
* current listener.
|
||||
*/
|
||||
this.Emulator = function(sendToEmulatorFn) {
|
||||
this.sendToEmulator = sendToEmulatorFn;
|
||||
this.Emulator = function(sendFn) {
|
||||
this.send = sendFn;
|
||||
this.cbs = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Instruct the client to run an Android emulator command.
|
||||
* Pops a callback off the stack if found. Otherwise this is a no-op.
|
||||
*
|
||||
* @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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Instruct the client to execute Android emulator shell arguments.
|
||||
* @param {number} id
|
||||
* Unique ID associated with the callback.
|
||||
*
|
||||
* @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.
|
||||
* @return {?function(Object)}
|
||||
* Callback function that takes an emulator response message as
|
||||
* an argument.
|
||||
*/
|
||||
Emulator.prototype.shell = function(args, resCb, errCb) {
|
||||
assertDefined(args, "runEmulatorShell");
|
||||
this.sendToEmulator(
|
||||
"runEmulatorShell", {emulator_shell: args}, resCb, errCb);
|
||||
};
|
||||
|
||||
Emulator.prototype.processMessage = function(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.command, resCb, errCb);
|
||||
break;
|
||||
|
||||
case "Marionette:runEmulatorShell":
|
||||
this.shell(msg.json.arguments, resCb, errCb);
|
||||
break;
|
||||
Emulator.prototype.popCallback = function(id) {
|
||||
let f, fi;
|
||||
for (let i = 0; i < this.cbs.length; ++i) {
|
||||
if (this.cbs[i].id == id) {
|
||||
f = this.cbs[i];
|
||||
fi = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!f) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.cbs.splice(fi, 1);
|
||||
return f;
|
||||
};
|
||||
|
||||
Emulator.prototype.resultCallback = function(msgId) {
|
||||
return res => this.sendResult({result: res, id: msgId});
|
||||
/**
|
||||
* Pushes callback on to the stack.
|
||||
*
|
||||
* @param {function(Object)} cb
|
||||
* Callback function that takes an emulator response message as
|
||||
* an argument.
|
||||
*/
|
||||
Emulator.prototype.pushCallback = function(cb) {
|
||||
cb.send_ = this.sendFn;
|
||||
this.cbs.push(cb);
|
||||
};
|
||||
|
||||
Emulator.prototype.errorCallback = function(msgId) {
|
||||
return err => this.sendResult({error: err, id: msgId});
|
||||
/**
|
||||
* Encapsulates a callback to the emulator and provides an execution
|
||||
* environment for them.
|
||||
*
|
||||
* Each callback is assigned a unique identifier, id, that can be used
|
||||
* to retrieve them from Emulator's stack using popCallback.
|
||||
*
|
||||
* The onresult event listener is triggered when a result arrives on
|
||||
* the callback.
|
||||
*
|
||||
* The onerror event listener is triggered when an error occurs during
|
||||
* the execution of that callback.
|
||||
*/
|
||||
this.EmulatorCallback = function() {
|
||||
this.id = uuidGen.generateUUID().toString();
|
||||
this.onresult = null;
|
||||
this.onerror = null;
|
||||
this.send_ = null;
|
||||
};
|
||||
|
||||
Emulator.prototype.sendResult = function(msg) {
|
||||
// sendToListener set explicitly in GeckoDriver's ctor
|
||||
this.sendToListener("emulatorCmdResult", msg);
|
||||
EmulatorCallback.prototype.command = function(cmd, cb) {
|
||||
this.onresult = cb;
|
||||
this.send_({emulator_cmd: cmd, id: this.id});
|
||||
};
|
||||
|
||||
/** Receives IPC messages from the listener. */
|
||||
Emulator.prototype.receiveMessage = function(msg) {
|
||||
EmulatorCallback.prototype.shell = function(args, cb) {
|
||||
this.onresult = cb;
|
||||
this.send_({emulator_shell: args, id: this.id});
|
||||
};
|
||||
|
||||
EmulatorCallback.prototype.result = function(msg) {
|
||||
if (this.send_ === null) {
|
||||
throw new TypeError(
|
||||
"EmulatorCallback must be registered with Emulator to fire");
|
||||
}
|
||||
|
||||
try {
|
||||
this.processMessage(msg);
|
||||
if (!this.onresult) {
|
||||
return;
|
||||
}
|
||||
this.onresult(msg.result);
|
||||
} catch (e) {
|
||||
this.sendResult({error: `${e.name}: ${e.message}`, id: msg.json.id});
|
||||
if (this.onerror) {
|
||||
this.onerror(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -109,24 +109,6 @@ error.stringify = function(err) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Marshal an Error to a JSON structure.
|
||||
*
|
||||
* @param {Error} err
|
||||
* The Error to serialise.
|
||||
*
|
||||
* @return {Object.<string, Object>}
|
||||
* JSON structure with the keys "error", "message", and "stacktrace".
|
||||
*/
|
||||
error.toJson = function(err) {
|
||||
let json = {
|
||||
error: err.status,
|
||||
message: err.message || null,
|
||||
stacktrace: err.stack || null,
|
||||
};
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* WebDriverError is the prototypal parent of all WebDriver errors.
|
||||
* It should not be used directly, as it does not correspond to a real
|
||||
@ -234,7 +216,7 @@ this.NoAlertOpenError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "NoAlertOpenError";
|
||||
this.status = "no such alert";
|
||||
};
|
||||
}
|
||||
NoAlertOpenError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.NoSuchElementError = function(msg) {
|
||||
|
@ -185,8 +185,8 @@ FrameManager.prototype = {
|
||||
mm.addWeakMessageListener("Marionette:error", this.server);
|
||||
mm.addWeakMessageListener("Marionette:emitTouchEvent", this.server);
|
||||
mm.addWeakMessageListener("Marionette:log", this.server);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorCmd", this.server.emulator);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorShell", this.server.emulator);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorCmd", this.server);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorShell", this.server);
|
||||
mm.addWeakMessageListener("Marionette:shareData", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
@ -217,8 +217,8 @@ FrameManager.prototype = {
|
||||
mm.removeWeakMessageListener("Marionette:error", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:log", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:shareData", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server.emulator);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.server.emulator);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
|
@ -16,7 +16,7 @@ marionette.jar:
|
||||
content/EventUtils.js (EventUtils.js)
|
||||
content/ChromeUtils.js (ChromeUtils.js)
|
||||
content/error.js (error.js)
|
||||
content/message.js (message.js)
|
||||
content/command.js (command.js)
|
||||
content/dispatcher.js (dispatcher.js)
|
||||
content/emulator.js (emulator.js)
|
||||
content/modal.js (modal.js)
|
||||
|
@ -1903,48 +1903,40 @@ function getAppCacheStatus(msg) {
|
||||
}
|
||||
|
||||
// emulator callbacks
|
||||
var _emu_cb_id = 0;
|
||||
var _emu_cbs = {};
|
||||
|
||||
function runEmulatorCmd(cmd, callback) {
|
||||
logger.info("listener runEmulatorCmd cmd=" + cmd);
|
||||
if (callback) {
|
||||
_emu_cbs[asyncTestCommandId] = callback;
|
||||
_emu_cbs[_emu_cb_id] = callback;
|
||||
}
|
||||
sendAsyncMessage("Marionette:runEmulatorCmd",
|
||||
{command: cmd, id: asyncTestCommandId});
|
||||
sendAsyncMessage("Marionette:runEmulatorCmd", {emulator_cmd: cmd, id: _emu_cb_id});
|
||||
_emu_cb_id += 1;
|
||||
}
|
||||
|
||||
function runEmulatorShell(args, callback) {
|
||||
if (callback) {
|
||||
_emu_cbs[asyncTestCommandId] = callback;
|
||||
_emu_cbs[_emu_cb_id] = callback;
|
||||
}
|
||||
sendAsyncMessage("Marionette:runEmulatorShell",
|
||||
{arguments: args, id: asyncTestCommandId});
|
||||
sendAsyncMessage("Marionette:runEmulatorShell", {emulator_shell: args, id: _emu_cb_id});
|
||||
_emu_cb_id += 1;
|
||||
}
|
||||
|
||||
function emulatorCmdResult(msg) {
|
||||
let {error, result, id} = msg.json;
|
||||
|
||||
if (error) {
|
||||
let err = new JavaScriptError(error);
|
||||
sendError(err, id);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = msg.json;
|
||||
if (!sandboxes[sandboxName]) {
|
||||
return;
|
||||
}
|
||||
let cb = _emu_cbs[id];
|
||||
delete _emu_cbs[id];
|
||||
let cb = _emu_cbs[message.id];
|
||||
delete _emu_cbs[message.id];
|
||||
if (!cb) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cb(result);
|
||||
cb(message.result);
|
||||
} catch (e) {
|
||||
let err = new JavaScriptError(e);
|
||||
sendError(err, id);
|
||||
sendError(e, -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,288 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"Command",
|
||||
"Message",
|
||||
"MessageOrigin",
|
||||
"Response",
|
||||
];
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
this.MessageOrigin = {
|
||||
Client: 0,
|
||||
Server: 1,
|
||||
};
|
||||
|
||||
this.Message = {};
|
||||
|
||||
/**
|
||||
* Converts a data packet into a Command or Response type.
|
||||
*
|
||||
* @param {Array.<number, number, ?, ?>} data
|
||||
* A four element array where the elements, in sequence, signifies
|
||||
* message type, message ID, method name or error, and parameters
|
||||
* or result.
|
||||
*
|
||||
* @return {(Command,Response)}
|
||||
* Based on the message type, a Command or Response instance.
|
||||
*
|
||||
* @throws {TypeError}
|
||||
* If the message type is not recognised.
|
||||
*/
|
||||
Message.fromMsg = function(data) {
|
||||
switch (data[0]) {
|
||||
case Command.TYPE:
|
||||
return Command.fromMsg(data);
|
||||
|
||||
case Response.TYPE:
|
||||
return Response.fromMsg(data);
|
||||
|
||||
default:
|
||||
throw new TypeError(
|
||||
"Unrecognised message type in packet: " + JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A command is a request from the client to run a series of remote end
|
||||
* steps and return a fitting response.
|
||||
*
|
||||
* The command can be synthesised from the message passed over the
|
||||
* Marionette socket using the {@code fromMsg} function. The format of
|
||||
* a message is:
|
||||
*
|
||||
* [type, id, name, params]
|
||||
*
|
||||
* where
|
||||
*
|
||||
* type:
|
||||
* Must be zero (integer). Zero means that this message is a command.
|
||||
*
|
||||
* id:
|
||||
* Number used as a sequence number. The server replies with a
|
||||
* requested id.
|
||||
*
|
||||
* name:
|
||||
* String representing the command name with an associated set of
|
||||
* remote end steps.
|
||||
*
|
||||
* params:
|
||||
* Object of command function arguments. The keys of this object
|
||||
* must be strings, but the values can be arbitrary values.
|
||||
*
|
||||
* A command has an associated message {@code id} that prevents the
|
||||
* dispatcher from sending responses in the wrong order.
|
||||
*
|
||||
* The command may also have optional error- and result handlers that
|
||||
* are called when the client returns with a response. These are
|
||||
* {@code function onerror({Object})}, {@code function onresult({Object})},
|
||||
* and {@code function onresult({Response})}.
|
||||
*
|
||||
* @param {number} msgId
|
||||
* Message ID unique identifying this message.
|
||||
* @param {string} name
|
||||
* Command name.
|
||||
* @param {Object<string, ?>} params
|
||||
* Command parameters.
|
||||
*/
|
||||
this.Command = class {
|
||||
constructor(msgId, name, params={}) {
|
||||
this.id = msgId;
|
||||
this.name = name;
|
||||
this.parameters = params;
|
||||
|
||||
this.onerror = null;
|
||||
this.onresult = null;
|
||||
|
||||
this.origin = MessageOrigin.Client;
|
||||
this.sent = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the error- or result handler associated with this command.
|
||||
* This function can be replaced with a custom response handler.
|
||||
*
|
||||
* @param {Response} resp
|
||||
* The response to pass on to the result or error to the
|
||||
* {@code onerror} or {@code onresult} handlers to.
|
||||
*/
|
||||
onresponse(resp) {
|
||||
if (resp.error && this.onerror) {
|
||||
this.onerror(resp.error);
|
||||
} else if (resp.body && this.onresult) {
|
||||
this.onresult(resp.body);
|
||||
}
|
||||
}
|
||||
|
||||
toMsg() {
|
||||
return [Command.TYPE, this.id, this.name, this.parameters];
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "Command {id: " + this.id + ", " +
|
||||
"name: " + JSON.stringify(this.name) + ", " +
|
||||
"parameters: " + JSON.stringify(this.parameters) + "}"
|
||||
}
|
||||
|
||||
static fromMsg(msg) {
|
||||
return new Command(msg[1], msg[2], msg[3]);
|
||||
}
|
||||
};
|
||||
|
||||
Command.TYPE = 0;
|
||||
|
||||
|
||||
const validator = {
|
||||
exclusionary: {
|
||||
"capabilities": ["error", "value"],
|
||||
"error": ["value", "sessionId", "capabilities"],
|
||||
"sessionId": ["error", "value"],
|
||||
"value": ["error", "sessionId", "capabilities"],
|
||||
},
|
||||
|
||||
set: function(obj, prop, val) {
|
||||
let tests = this.exclusionary[prop];
|
||||
if (tests) {
|
||||
for (let t of tests) {
|
||||
if (obj.hasOwnProperty(t)) {
|
||||
throw new TypeError(`${t} set, cannot set ${prop}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj[prop] = val;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The response body is exposed as an argument to commands.
|
||||
* Commands can set fields on the body through defining properties.
|
||||
*
|
||||
* Setting properties invokes a validator that performs tests for
|
||||
* mutually exclusionary fields on the input against the existing data
|
||||
* in the body.
|
||||
*
|
||||
* For example setting the {@code error} property on the body when
|
||||
* {@code value}, {@code sessionId}, or {@code capabilities} have been
|
||||
* set previously will cause an error.
|
||||
*/
|
||||
this.ResponseBody = () => new Proxy({}, validator);
|
||||
|
||||
/**
|
||||
* Represents the response returned from the remote end after execution
|
||||
* of its corresponding command.
|
||||
*
|
||||
* The response is a mutable object passed to each command for
|
||||
* modification through the available setters. To send data in a response,
|
||||
* you modify the body property on the response. The body property can
|
||||
* also be replaced completely.
|
||||
*
|
||||
* The response is sent implicitly by CommandProcessor when a command
|
||||
* has finished executing, and any modifications made subsequent to that
|
||||
* will have no effect.
|
||||
*
|
||||
* @param {number} msgId
|
||||
* Message ID tied to the corresponding command request this is a
|
||||
* response for.
|
||||
* @param {function(Response|Message)} respHandler
|
||||
* Function callback called on sending the response.
|
||||
*/
|
||||
this.Response = class {
|
||||
constructor(msgId, respHandler) {
|
||||
this.id = msgId;
|
||||
|
||||
this.error = null;
|
||||
this.body = ResponseBody();
|
||||
|
||||
this.origin = MessageOrigin.Server;
|
||||
this.sent = false;
|
||||
|
||||
this.respHandler_ = respHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends response conditionally, given a predicate.
|
||||
*
|
||||
* @param {function(Response): boolean} predicate
|
||||
* A predicate taking a Response object and returning a boolean.
|
||||
*/
|
||||
sendConditionally(predicate) {
|
||||
if (predicate(this)) {
|
||||
this.send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends response using the response handler provided on construction.
|
||||
*
|
||||
* @throws {RangeError}
|
||||
* If the response has already been sent.
|
||||
*/
|
||||
send() {
|
||||
if (this.sent) {
|
||||
throw new RangeError("Response has already been sent: " + this);
|
||||
}
|
||||
this.respHandler_(this);
|
||||
this.sent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send given Error to client.
|
||||
*
|
||||
* Turns the response into an error response, clears any previously
|
||||
* set body data, and sends it using the response handler provided
|
||||
* on construction.
|
||||
*
|
||||
* @param {Error} err
|
||||
* The Error instance to send.
|
||||
*
|
||||
* @throws {Error}
|
||||
* If the {@code error} is not a WebDriverError, the error is
|
||||
* propagated.
|
||||
*/
|
||||
sendError(err) {
|
||||
let wd = error.isWebDriverError(err);
|
||||
let we = wd ? err : new WebDriverError(err.message);
|
||||
|
||||
this.error = error.toJson(err);
|
||||
this.body = null;
|
||||
this.send();
|
||||
|
||||
// propagate errors that are implementation problems
|
||||
if (!wd) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
toMsg() {
|
||||
return [Response.TYPE, this.id, this.error, this.body];
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "Response {id: " + this.id + ", " +
|
||||
"error: " + JSON.stringify(this.error) + ", " +
|
||||
"body: " + JSON.stringify(this.body) + "}";
|
||||
}
|
||||
|
||||
static fromMsg(msg) {
|
||||
let resp = new Response(msg[1], null);
|
||||
resp.error = msg[2];
|
||||
resp.body = msg[3];
|
||||
return resp;
|
||||
}
|
||||
};
|
||||
|
||||
Response.TYPE = 1;
|
@ -93,8 +93,7 @@ MarionetteServer.prototype.driverFactory = function(emulator) {
|
||||
Services.io.offline = false;
|
||||
}
|
||||
|
||||
let stopSignal = () => this.stop();
|
||||
return new GeckoDriver(appName, device, stopSignal, emulator);
|
||||
return new GeckoDriver(appName, device, emulator);
|
||||
};
|
||||
|
||||
MarionetteServer.prototype.start = function() {
|
||||
@ -130,17 +129,20 @@ MarionetteServer.prototype.onSocketAccepted = function(
|
||||
let transport = new DebuggerTransport(input, output);
|
||||
let connId = "conn" + this.nextConnId++;
|
||||
|
||||
let dispatcher = new Dispatcher(connId, transport, this.driverFactory.bind(this));
|
||||
let stopSignal = () => this.stop();
|
||||
let dispatcher = new Dispatcher(connId, transport, this.driverFactory, stopSignal);
|
||||
dispatcher.onclose = this.onConnectionClosed.bind(this);
|
||||
this.conns[connId] = dispatcher;
|
||||
|
||||
logger.info(`Accepted connection ${connId} from ${clientSocket.host}:${clientSocket.port}`);
|
||||
|
||||
// Create a root actor for the connection and send the hello packet
|
||||
dispatcher.sayHello();
|
||||
transport.ready();
|
||||
};
|
||||
|
||||
MarionetteServer.prototype.onConnectionClosed = function(conn) {
|
||||
let id = conn.connId;
|
||||
let id = conn.id;
|
||||
delete this.conns[id];
|
||||
logger.info(`Closed connection ${id}`);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user