mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
c143de5656
Message sequencing allows Marionette to provide an asynchronous, parallel pipelining user-facing interface, limit chances of payload race conditions, and remove stylistic inconsistencies in how commands and responses are dispatched internally. Clients that deliver a blocking WebDriver interface are still be expected to not send further command requests before the response from the last command has come back, but if they still happen to do so because of programming error or otherwise, no harm will be done. This will guard against bugs such as bug 1207125. This patch formalises the command and response concepts, and applies these concepts to emulator callbacks. Through the new message format, Marionette is able to provide two-way parallel communication. In other words, the server will be able to instruct the client to perform a command in a non ad-hoc way. runEmulatorCmd and runEmulatorShell are both turned into command instructions originating from the server. This resolves a lot of technical debt in the server code because they are no longer special-cased to circumvent the dispatching technique used for all other commands; commands may originate from either the client or the server providing parallel pipelining enforced through message sequencing: client server | | msgid=1 |----------->| | command | | | msgid=2 |<-----------| | command | | | msgid=2 |----------->| | response | | | msgid=1 |<-----------| | response | | | The protocol now consists of a "Command" message and the corresponding "Response" message. A "Response" message must always be sent in reply to a "Command" message. This bumps the Marionette protocol level to 3. r=dburns r=jgriffin
155 lines
4.7 KiB
JavaScript
155 lines
4.7 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";
|
|
|
|
var {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
|
|
|
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
|
|
const ServerSocket = CC("@mozilla.org/network/server-socket;1", "nsIServerSocket", "initSpecialConnection");
|
|
|
|
Cu.import("resource://gre/modules/Log.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
Cu.import("chrome://marionette/content/dispatcher.js");
|
|
Cu.import("chrome://marionette/content/driver.js");
|
|
Cu.import("chrome://marionette/content/elements.js");
|
|
Cu.import("chrome://marionette/content/simpletest.js");
|
|
|
|
// Bug 1083711: Load transport.js as an SDK module instead of subscript
|
|
loader.loadSubScript("resource://devtools/shared/transport/transport.js");
|
|
|
|
// Preserve this import order:
|
|
var events = {};
|
|
loader.loadSubScript("chrome://marionette/content/EventUtils.js", events);
|
|
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", events);
|
|
loader.loadSubScript("chrome://marionette/content/frame-manager.js");
|
|
|
|
const logger = Log.repository.getLogger("Marionette");
|
|
|
|
this.EXPORTED_SYMBOLS = ["MarionetteServer"];
|
|
const CONTENT_LISTENER_PREF = "marionette.contentListener";
|
|
|
|
/**
|
|
* Bootstraps Marionette and handles incoming client connections.
|
|
*
|
|
* Once started, it opens a TCP socket sporting the debugger transport
|
|
* protocol on the provided port. For every new client a Dispatcher is
|
|
* created.
|
|
*
|
|
* @param {number} port
|
|
* Port for server to listen to.
|
|
* @param {boolean} forceLocal
|
|
* Listen only to connections from loopback if true. If false,
|
|
* accept all connections.
|
|
*/
|
|
this.MarionetteServer = function(port, forceLocal) {
|
|
this.port = port;
|
|
this.forceLocal = forceLocal;
|
|
this.conns = {};
|
|
this.nextConnId = 0;
|
|
this.alive = false;
|
|
};
|
|
|
|
/**
|
|
* Function that takes an Emulator and produces a GeckoDriver.
|
|
*
|
|
* Determines application name and device type to initialise the driver
|
|
* with. Also bypasses offline status if the device is a qemu or panda
|
|
* type device.
|
|
*
|
|
* @return {GeckoDriver}
|
|
* A driver instance.
|
|
*/
|
|
MarionetteServer.prototype.driverFactory = function(emulator) {
|
|
let appName = isMulet() ? "B2G" : Services.appinfo.name;
|
|
let qemu = "0";
|
|
let device = null;
|
|
let bypassOffline = false;
|
|
|
|
try {
|
|
Cu.import("resource://gre/modules/systemlibs.js");
|
|
qemu = libcutils.property_get("ro.kernel.qemu");
|
|
logger.debug("B2G emulator: " + (qemu == "1" ? "yes" : "no"));
|
|
device = libcutils.property_get("ro.product.device");
|
|
logger.debug("Device detected is " + device);
|
|
bypassOffline = (qemu == "1" || device == "panda");
|
|
} catch (e) {}
|
|
|
|
if (qemu == "1") {
|
|
device = "qemu";
|
|
}
|
|
if (!device) {
|
|
device = "desktop";
|
|
}
|
|
|
|
Services.prefs.setBoolPref(CONTENT_LISTENER_PREF, false);
|
|
|
|
if (bypassOffline) {
|
|
logger.debug("Bypassing offline status");
|
|
Services.prefs.setBoolPref("network.gonk.manage-offline-status", false);
|
|
Services.io.manageOfflineStatus = false;
|
|
Services.io.offline = false;
|
|
}
|
|
|
|
let stopSignal = () => this.stop();
|
|
return new GeckoDriver(appName, device, stopSignal, emulator);
|
|
};
|
|
|
|
MarionetteServer.prototype.start = function() {
|
|
if (this.alive) {
|
|
return;
|
|
}
|
|
let flags = Ci.nsIServerSocket.KeepWhenOffline;
|
|
if (this.forceLocal) {
|
|
flags |= Ci.nsIServerSocket.LoopbackOnly;
|
|
}
|
|
this.listener = new ServerSocket(this.port, flags, 0);
|
|
this.listener.asyncListen(this);
|
|
this.alive = true;
|
|
};
|
|
|
|
MarionetteServer.prototype.stop = function() {
|
|
if (!this.alive) {
|
|
return;
|
|
}
|
|
this.closeListener();
|
|
this.alive = false;
|
|
};
|
|
|
|
MarionetteServer.prototype.closeListener = function() {
|
|
this.listener.close();
|
|
this.listener = null;
|
|
};
|
|
|
|
MarionetteServer.prototype.onSocketAccepted = function(
|
|
serverSocket, clientSocket) {
|
|
let input = clientSocket.openInputStream(0, 0, 0);
|
|
let output = clientSocket.openOutputStream(0, 0, 0);
|
|
let transport = new DebuggerTransport(input, output);
|
|
let connId = "conn" + this.nextConnId++;
|
|
|
|
let dispatcher = new Dispatcher(connId, transport, this.driverFactory.bind(this));
|
|
dispatcher.onclose = this.onConnectionClosed.bind(this);
|
|
this.conns[connId] = dispatcher;
|
|
|
|
logger.info(`Accepted connection ${connId} from ${clientSocket.host}:${clientSocket.port}`);
|
|
dispatcher.sayHello();
|
|
transport.ready();
|
|
};
|
|
|
|
MarionetteServer.prototype.onConnectionClosed = function(conn) {
|
|
let id = conn.connId;
|
|
delete this.conns[id];
|
|
logger.info(`Closed connection ${id}`);
|
|
};
|
|
|
|
function isMulet() {
|
|
try {
|
|
return Services.prefs.getBoolPref("b2g.is_mulet");
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|