2015-03-18 12:27:29 +00:00
|
|
|
/* 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";
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
const {interfaces: Ci, utils: Cu} = Components;
|
2015-03-18 12:27:29 +00:00
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/Log.jsm");
|
2016-01-18 22:05:30 +00:00
|
|
|
Cu.import("resource://gre/modules/Preferences.jsm");
|
2015-03-18 12:27:29 +00:00
|
|
|
Cu.import("resource://gre/modules/Task.jsm");
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
Cu.import("chrome://marionette/content/driver.js");
|
2015-03-18 12:27:29 +00:00
|
|
|
Cu.import("chrome://marionette/content/emulator.js");
|
|
|
|
Cu.import("chrome://marionette/content/error.js");
|
2015-09-26 16:12:01 +00:00
|
|
|
Cu.import("chrome://marionette/content/message.js");
|
2015-03-18 12:27:29 +00:00
|
|
|
|
|
|
|
this.EXPORTED_SYMBOLS = ["Dispatcher"];
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
const PROTOCOL_VERSION = 3;
|
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 10:26:58 +00:00
|
|
|
|
2015-03-18 12:27:29 +00:00
|
|
|
const logger = Log.repository.getLogger("Marionette");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
* a GeckoDriver.
|
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
this.Dispatcher = function(connId, transport, driverFactory) {
|
|
|
|
this.connId = connId;
|
2015-03-18 12:27:29 +00:00
|
|
|
this.conn = transport;
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
// transport hooks are Dispatcher#onPacket
|
|
|
|
// and Dispatcher#onClosed
|
|
|
|
this.conn.hooks = this;
|
|
|
|
|
2015-03-18 12:27:29 +00:00
|
|
|
// callback for when connection is closed
|
|
|
|
this.onclose = null;
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
// last received/sent message ID
|
|
|
|
this.lastId = 0;
|
2015-03-18 12:27:29 +00:00
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
this.emulator = new Emulator(this.sendEmulator.bind(this));
|
2015-03-18 12:27:29 +00:00
|
|
|
this.driver = driverFactory(this.emulator);
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
// lookup of commands sent by server to client by message ID
|
|
|
|
this.commands_ = new Map();
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Debugger transport callback that cleans up
|
|
|
|
* after a connection is closed.
|
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
Dispatcher.prototype.onClosed = function(reason) {
|
2015-03-18 12:27:29 +00:00
|
|
|
this.driver.sessionTearDown();
|
|
|
|
if (this.onclose) {
|
|
|
|
this.onclose(this);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
|
|
|
|
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);
|
2015-03-18 12:27:29 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-09-26 16:12:01 +00:00
|
|
|
* 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.
|
2015-03-18 12:27:29 +00:00
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
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));
|
2015-11-26 22:47:34 +00:00
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
req.then(sendResponse, sendError).catch(error.report);
|
|
|
|
};
|
2015-11-26 22:47:34 +00:00
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
Dispatcher.prototype.sendError = function(err, cmdId) {
|
|
|
|
let resp = new Response(cmdId, this.send.bind(this));
|
|
|
|
resp.sendError(err);
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Convenience methods:
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-03-18 12:27:29 +00:00
|
|
|
Dispatcher.prototype.sayHello = function() {
|
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 10:26:58 +00:00
|
|
|
let whatHo = {
|
|
|
|
applicationType: "gecko",
|
|
|
|
marionetteProtocol: PROTOCOL_VERSION,
|
|
|
|
};
|
2015-09-26 16:12:01 +00:00
|
|
|
this.sendRaw(whatHo);
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
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);
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delegates message to client or emulator based on the provided
|
|
|
|
* {@code cmdId}. The message is sent over the debugger transport socket.
|
|
|
|
*
|
|
|
|
* The command ID is a unique identifier assigned to the client's request
|
|
|
|
* that is used to distinguish the asynchronous responses.
|
|
|
|
*
|
|
|
|
* Whilst responses to commands are synchronous and must be sent in the
|
|
|
|
* correct order, emulator callbacks are more transparent and can be sent
|
|
|
|
* at any time. These callbacks won't change the current command state.
|
|
|
|
*
|
2015-09-26 16:12:01 +00:00
|
|
|
* @param {Command,Response} msg
|
|
|
|
* The command or response to send.
|
2015-03-18 12:27:29 +00:00
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
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);
|
2015-03-18 12:27:29 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 10:26:58 +00:00
|
|
|
// Low-level methods:
|
|
|
|
|
2015-03-18 12:27:29 +00:00
|
|
|
/**
|
2015-09-26 16:12:01 +00:00
|
|
|
* Send command to emulator over the debugger transport socket.
|
|
|
|
*
|
|
|
|
* @param {Command} cmd
|
|
|
|
* The command to issue to the emulator.
|
2015-03-18 12:27:29 +00:00
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
Dispatcher.prototype.sendToEmulator = function(cmd) {
|
|
|
|
this.sendMessage(cmd);
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-09-26 16:12:01 +00:00
|
|
|
* Send given response to the client over the debugger transport socket.
|
2015-03-18 12:27:29 +00:00
|
|
|
*
|
2015-09-26 16:12:01 +00:00
|
|
|
* @param {Response} resp
|
|
|
|
* The response to send back to the client.
|
2015-03-18 12:27:29 +00:00
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
Dispatcher.prototype.sendToClient = function(resp) {
|
2015-03-19 21:12:58 +00:00
|
|
|
this.driver.responseCompleted();
|
2015-09-26 16:12:01 +00:00
|
|
|
this.sendMessage(resp);
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-09-26 16:12:01 +00:00
|
|
|
* Marshal message to the Marionette message format and send it.
|
|
|
|
*
|
|
|
|
* @param {Command,Response} msg
|
|
|
|
* The message to send.
|
2015-03-18 12:27:29 +00:00
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
Dispatcher.prototype.sendMessage = function(msg) {
|
|
|
|
this.log_(msg);
|
|
|
|
let payload = msg.toMsg();
|
|
|
|
this.sendRaw(payload);
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-09-26 16:12:01 +00:00
|
|
|
* Send the given payload over the debugger transport socket to the
|
|
|
|
* connected client.
|
2015-03-18 12:27:29 +00:00
|
|
|
*
|
2015-09-26 16:12:01 +00:00
|
|
|
* @param {Object} payload
|
|
|
|
* The payload to ship.
|
2015-03-18 12:27:29 +00:00
|
|
|
*/
|
2015-09-26 16:12:01 +00:00
|
|
|
Dispatcher.prototype.sendRaw = function(payload) {
|
|
|
|
this.conn.send(payload);
|
2015-11-26 22:47:34 +00:00
|
|
|
};
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
Dispatcher.prototype.log_ = function(msg) {
|
|
|
|
let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
|
2016-01-08 16:01:07 +00:00
|
|
|
let s = JSON.stringify(msg.toMsg());
|
|
|
|
logger.trace(this.connId + a + s);
|
2015-03-18 12:27:29 +00:00
|
|
|
};
|