123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- /* 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 {interfaces: Ci, utils: Cu} = Components;
- Cu.import("resource://gre/modules/Log.jsm");
- Cu.import("resource://gre/modules/Preferences.jsm");
- Cu.import("resource://gre/modules/Task.jsm");
- Cu.import("chrome://marionette/content/assert.js");
- Cu.import("chrome://marionette/content/driver.js");
- Cu.import("chrome://marionette/content/error.js");
- Cu.import("chrome://marionette/content/message.js");
- this.EXPORTED_SYMBOLS = ["Dispatcher"];
- const PROTOCOL_VERSION = 3;
- 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(): GeckoDriver} driverFactory
- * A factory function that 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.driver = driverFactory();
- // lookup of commands sent by server to client by message ID
- this.commands_ = new Map();
- };
- /**
- * Debugger transport callback that cleans up
- * after a connection is closed.
- */
- Dispatcher.prototype.onClosed = function (reason) {
- this.driver.deleteSession();
- 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);
- 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);
- }
- };
- /**
- * 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.
- */
- 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);
- }
- if (cmd.name !== "newSession") {
- assert.session(this.driver);
- }
- 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);
- };
- Dispatcher.prototype.sendError = function (err, cmdId) {
- let resp = new Response(cmdId, this.send.bind(this));
- 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);
- };
- /**
- * Delegates message to client 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.
- *
- * @param {Command,Response} msg
- * The command or response to send.
- */
- 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);
- }
- };
- // Low-level methods:
- /**
- * Send given response to the client over the debugger transport socket.
- *
- * @param {Response} resp
- * The response to send back to the client.
- */
- Dispatcher.prototype.sendToClient = function (resp) {
- this.driver.responseCompleted();
- this.sendMessage(resp);
- };
- /**
- * Marshal message to the Marionette message format and send it.
- *
- * @param {Command,Response} msg
- * The message to send.
- */
- 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) {
- this.conn.send(payload);
- };
- Dispatcher.prototype.log_ = function (msg) {
- let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
- let s = JSON.stringify(msg.toMsg());
- logger.trace(this.connId + a + s);
- };
|