зеркало из https://github.com/mozilla/gecko-dev.git
377 строки
9.6 KiB
JavaScript
377 строки
9.6 KiB
JavaScript
/* 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";
|
|
|
|
ChromeUtils.import("chrome://marionette/content/assert.js");
|
|
ChromeUtils.import("chrome://marionette/content/error.js");
|
|
const {truncate} = ChromeUtils.import("chrome://marionette/content/format.js", {});
|
|
|
|
this.EXPORTED_SYMBOLS = [
|
|
"Command",
|
|
"Message",
|
|
"Response",
|
|
];
|
|
|
|
/** Representation of the packets transproted over the wire. */
|
|
class Message {
|
|
/**
|
|
* @param {number} messageID
|
|
* Message ID unique identifying this message.
|
|
*/
|
|
constructor(messageID) {
|
|
this.id = assert.integer(messageID);
|
|
}
|
|
|
|
toString() {
|
|
let content = JSON.stringify(this.toPacket());
|
|
return truncate`${content}`;
|
|
}
|
|
|
|
/**
|
|
* Converts a data packet into a {@link Command} or {@link Response}.
|
|
*
|
|
* @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 {Message}
|
|
* Based on the message type, a {@link Command} or {@link Response}
|
|
* instance.
|
|
*
|
|
* @throws {TypeError}
|
|
* If the message type is not recognised.
|
|
*/
|
|
static fromPacket(data) {
|
|
const [type] = data;
|
|
|
|
switch (type) {
|
|
case Command.Type:
|
|
return Command.fromPacket(data);
|
|
|
|
case Response.Type:
|
|
return Response.fromPacket(data);
|
|
|
|
default:
|
|
throw new TypeError(
|
|
"Unrecognised message type in packet: " + JSON.stringify(data));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Messages may originate from either the server or the client.
|
|
* Because the remote protocol is full duplex, both endpoints may be
|
|
* the origin of both commands and responses.
|
|
*
|
|
* @enum
|
|
* @see {@link Message}
|
|
*/
|
|
Message.Origin = {
|
|
/** Indicates that the message originates from the client. */
|
|
Client: 0,
|
|
/** Indicates that the message originates from the server. */
|
|
Server: 1,
|
|
};
|
|
|
|
/**
|
|
* 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 {@link fromPacket} function. The format of
|
|
* a message is:
|
|
*
|
|
* <pre>
|
|
* [<var>type</var>, <var>id</var>, <var>name</var>, <var>params</var>]
|
|
* </pre>
|
|
*
|
|
* where
|
|
*
|
|
* <dl>
|
|
* <dt><var>type</var> (integer)
|
|
* <dd>
|
|
* Must be zero (integer). Zero means that this message is
|
|
* a command.
|
|
*
|
|
* <dt><var>id</var> (integer)
|
|
* <dd>
|
|
* Integer used as a sequence number. The server replies with
|
|
* the same ID for the response.
|
|
*
|
|
* <dt><var>name</var> (string)
|
|
* <dd>
|
|
* String representing the command name with an associated set
|
|
* of remote end steps.
|
|
*
|
|
* <dt><var>params</var> (JSON Object or null)
|
|
* <dd>
|
|
* Object of command function arguments. The keys of this object
|
|
* must be strings, but the values can be arbitrary values.
|
|
* </dl>
|
|
*
|
|
* A command has an associated message <var>id</var> 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>,
|
|
* <code>function onresult({Object})</code>, and
|
|
* <code>function onresult({Response})</code>:
|
|
*
|
|
* @param {number} messageID
|
|
* Message ID unique identifying this message.
|
|
* @param {string} name
|
|
* Command name.
|
|
* @param {Object.<string, ?>} params
|
|
* Command parameters.
|
|
*/
|
|
class Command extends Message {
|
|
constructor(messageID, name, params = {}) {
|
|
super(messageID);
|
|
|
|
this.name = assert.string(name);
|
|
this.parameters = assert.object(params);
|
|
|
|
this.onerror = null;
|
|
this.onresult = null;
|
|
|
|
this.origin = Message.Origin.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</code> or <code>onresult</code> handlers to.
|
|
*/
|
|
onresponse(resp) {
|
|
if (this.onerror && resp.error) {
|
|
this.onerror(resp.error);
|
|
} else if (this.onresult && resp.body) {
|
|
this.onresult(resp.body);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encodes the command to a packet.
|
|
*
|
|
* @return {Array}
|
|
* Packet.
|
|
*/
|
|
toPacket() {
|
|
return [
|
|
Command.Type,
|
|
this.id,
|
|
this.name,
|
|
this.parameters,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Converts a data packet into {@link Command}.
|
|
*
|
|
* @param {Array.<number, number, ?, ?>} data
|
|
* A four element array where the elements, in sequence, signifies
|
|
* message type, message ID, command name, and parameters.
|
|
*
|
|
* @return {Command}
|
|
* Representation of packet.
|
|
*
|
|
* @throws {TypeError}
|
|
* If the message type is not recognised.
|
|
*/
|
|
static fromPacket(payload) {
|
|
let [type, msgID, name, params] = payload;
|
|
assert.that(n => n === Command.Type)(type);
|
|
|
|
// if parameters are given but null, treat them as undefined
|
|
if (params === null) {
|
|
params = undefined;
|
|
}
|
|
|
|
return new Command(msgID, name, params);
|
|
}
|
|
}
|
|
Command.Type = 0;
|
|
|
|
const validator = {
|
|
exclusionary: {
|
|
"capabilities": ["error", "value"],
|
|
"error": ["value", "sessionId", "capabilities"],
|
|
"sessionId": ["error", "value"],
|
|
"value": ["error", "sessionId", "capabilities"],
|
|
},
|
|
|
|
set(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</code> property on
|
|
* the body when <code>value</code>, <code>sessionId</code>, or
|
|
* <code>capabilities</code> have been set previously will cause
|
|
* an error.
|
|
*/
|
|
const ResponseBody = () => new Proxy({}, validator);
|
|
|
|
/**
|
|
* @callback ResponseCallback
|
|
*
|
|
* @param {Response} resp
|
|
* Response to handle.
|
|
*/
|
|
|
|
/**
|
|
* 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
|
|
* {@link server.TCPConnection#execute when a command has finished
|
|
* executing, and any modifications made subsequent to that will have
|
|
* no effect.
|
|
*
|
|
* @param {number} messageID
|
|
* Message ID tied to the corresponding command request this is
|
|
* a response for.
|
|
* @param {ResponseHandler} respHandler
|
|
* Function callback called on sending the response.
|
|
*/
|
|
class Response extends Message {
|
|
constructor(messageID, respHandler = () => {}) {
|
|
super(messageID);
|
|
|
|
this.respHandler_ = assert.callable(respHandler);
|
|
|
|
this.error = null;
|
|
this.body = ResponseBody();
|
|
|
|
this.origin = Message.Origin.Server;
|
|
this.sent = false;
|
|
}
|
|
|
|
/**
|
|
* 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 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 <var>err</var> is not a {@link WebDriverError}, the error
|
|
* is propagated, i.e. rethrown.
|
|
*/
|
|
sendError(err) {
|
|
this.error = error.wrap(err).toJSON();
|
|
this.body = null;
|
|
this.send();
|
|
|
|
// propagate errors which are implementation problems
|
|
if (!error.isWebDriverError(err)) {
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encodes the response to a packet.
|
|
*
|
|
* @return {Array}
|
|
* Packet.
|
|
*/
|
|
toPacket() {
|
|
return [
|
|
Response.Type,
|
|
this.id,
|
|
this.error,
|
|
this.body,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Converts a data packet into {@link Response}.
|
|
*
|
|
* @param {Array.<number, number, ?, ?>} data
|
|
* A four element array where the elements, in sequence, signifies
|
|
* message type, message ID, error, and result.
|
|
*
|
|
* @return {Response}
|
|
* Representation of packet.
|
|
*
|
|
* @throws {TypeError}
|
|
* If the message type is not recognised.
|
|
*/
|
|
static fromPacket(payload) {
|
|
let [type, msgID, err, body] = payload;
|
|
assert.that(n => n === Response.Type)(type);
|
|
|
|
let resp = new Response(msgID);
|
|
resp.error = assert.string(err);
|
|
|
|
resp.body = body;
|
|
return resp;
|
|
}
|
|
}
|
|
Response.Type = 1;
|
|
|
|
this.Message = Message;
|
|
this.Command = Command;
|
|
this.Response = Response;
|