зеркало из https://github.com/mozilla/gecko-dev.git
286 строки
8.6 KiB
JavaScript
286 строки
8.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/. */
|
|
|
|
import { WebSocketConnection } from "chrome://remote/content/shared/WebSocketConnection.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
|
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
|
processCapabilities:
|
|
"chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
|
|
quit: "chrome://remote/content/shared/Browser.sys.mjs",
|
|
RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
|
|
WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
|
lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
|
|
);
|
|
|
|
export class WebDriverBiDiConnection extends WebSocketConnection {
|
|
#sessionConfigFlags;
|
|
|
|
/**
|
|
* @param {WebSocket} webSocket
|
|
* The WebSocket server connection to wrap.
|
|
* @param {Connection} httpdConnection
|
|
* Reference to the httpd.js's connection needed for clean-up.
|
|
*/
|
|
constructor(webSocket, httpdConnection) {
|
|
super(webSocket, httpdConnection);
|
|
|
|
// Each connection has only a single associated WebDriver session.
|
|
this.session = null;
|
|
|
|
this.#sessionConfigFlags = new Set([
|
|
lazy.WebDriverSession.SESSION_FLAG_BIDI,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Perform required steps to end the session.
|
|
*/
|
|
endSession() {
|
|
// TODO Bug 1838269. Implement session ending logic
|
|
// for the case of classic + bidi session.
|
|
// We currently only support one session, see Bug 1720707.
|
|
lazy.RemoteAgent.webDriverBiDi.deleteSession();
|
|
}
|
|
|
|
/**
|
|
* Register a new WebDriver Session to forward the messages to.
|
|
*
|
|
* @param {Session} session
|
|
* The WebDriverSession to register.
|
|
*/
|
|
registerSession(session) {
|
|
if (this.session) {
|
|
throw new lazy.error.UnknownError(
|
|
"A WebDriver session has already been set"
|
|
);
|
|
}
|
|
|
|
this.session = session;
|
|
lazy.logger.debug(
|
|
`Connection ${this.id} attached to session ${session.id}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Unregister the already set WebDriver session.
|
|
*/
|
|
unregisterSession() {
|
|
if (!this.session) {
|
|
return;
|
|
}
|
|
|
|
this.session.removeConnection(this);
|
|
this.session = null;
|
|
}
|
|
|
|
/**
|
|
* Send an error back to the WebDriver BiDi client.
|
|
*
|
|
* @param {number} id
|
|
* Id of the packet which lead to an error.
|
|
* @param {Error} err
|
|
* Error object with `status`, `message` and `stack` attributes.
|
|
*/
|
|
sendError(id, err) {
|
|
const webDriverError = lazy.error.wrap(err);
|
|
|
|
this.send({
|
|
type: "error",
|
|
id,
|
|
error: webDriverError.status,
|
|
message: webDriverError.message,
|
|
stacktrace: webDriverError.stack,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Send an event coming from a module to the WebDriver BiDi client.
|
|
*
|
|
* @param {string} method
|
|
* The event name. This is composed by a module name, a dot character
|
|
* followed by the event name, e.g. `log.entryAdded`.
|
|
* @param {object} params
|
|
* A JSON-serializable object, which is the payload of this event.
|
|
*/
|
|
sendEvent(method, params) {
|
|
this.send({ type: "event", method, params });
|
|
|
|
if (Services.profiler?.IsActive()) {
|
|
ChromeUtils.addProfilerMarker(
|
|
"BiDi: Event",
|
|
{ category: "Remote-Protocol" },
|
|
method
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send the result of a call to a module's method back to the
|
|
* WebDriver BiDi client.
|
|
*
|
|
* @param {number} id
|
|
* The request id being sent by the client to call the module's method.
|
|
* @param {object} result
|
|
* A JSON-serializable object, which is the actual result.
|
|
*/
|
|
sendResult(id, result) {
|
|
result = typeof result !== "undefined" ? result : {};
|
|
this.send({ type: "success", id, result });
|
|
}
|
|
|
|
observe(subject, topic) {
|
|
switch (topic) {
|
|
case "quit-application-requested":
|
|
this.endSession();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Transport hooks
|
|
|
|
/**
|
|
* Called by the `transport` when the connection is closed.
|
|
*/
|
|
onConnectionClose() {
|
|
this.unregisterSession();
|
|
|
|
super.onConnectionClose();
|
|
}
|
|
|
|
/**
|
|
* Receive a packet from the WebSocket layer.
|
|
*
|
|
* This packet is sent by a WebDriver BiDi client and is meant to execute
|
|
* a particular method on a given module.
|
|
*
|
|
* @param {object} packet
|
|
* JSON-serializable object sent by the client
|
|
*/
|
|
async onPacket(packet) {
|
|
super.onPacket(packet);
|
|
|
|
const { id, method, params } = packet;
|
|
const startTime = Cu.now();
|
|
|
|
try {
|
|
// First check for mandatory field in the command packet
|
|
lazy.assert.positiveInteger(
|
|
id,
|
|
lazy.pprint`Expected "id" to be a postive integer, got ${id}`
|
|
);
|
|
lazy.assert.string(
|
|
method,
|
|
lazy.pprint`Expected "method" to be a string, got ${method}`
|
|
);
|
|
lazy.assert.object(
|
|
params,
|
|
lazy.pprint`Expected "params" to be an object, got ${params}`
|
|
);
|
|
|
|
// Extract the module and the command name out of `method` attribute
|
|
const { module, command } = splitMethod(method);
|
|
let result;
|
|
|
|
// Handle static commands first
|
|
if (module === "session" && command === "new") {
|
|
const processedCapabilities = lazy.processCapabilities(params);
|
|
|
|
result = await lazy.RemoteAgent.webDriverBiDi.createSession(
|
|
processedCapabilities,
|
|
this.#sessionConfigFlags,
|
|
this
|
|
);
|
|
|
|
// Until the spec (see: https://github.com/w3c/webdriver/issues/1834)
|
|
// is updated to specify what should be the default value for bidi session,
|
|
// remove this capability from the return value when it's not provided by a client.
|
|
if (!("unhandledPromptBehavior" in processedCapabilities)) {
|
|
// We don't want to modify the original `capabilities` field
|
|
// because it points to an original Capabilities object used by the session.
|
|
// Since before the result is sent to a client we're going anyway to call
|
|
// `JSON.stringify` on `result` which will call `toJSON` method recursively,
|
|
// we can call it already here for the `capabilities` property
|
|
// to update only the command response object.
|
|
result.capabilities = result.capabilities.toJSON();
|
|
delete result.capabilities.unhandledPromptBehavior;
|
|
}
|
|
} else if (module === "session" && command === "status") {
|
|
result = lazy.RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
|
|
} else {
|
|
lazy.assert.session(this.session);
|
|
|
|
// Bug 1741854 - Workaround to deny internal methods to be called
|
|
if (command.startsWith("_")) {
|
|
throw new lazy.error.UnknownCommandError(method);
|
|
}
|
|
|
|
// Finally, instruct the session to execute the command
|
|
result = await this.session.execute(module, command, params);
|
|
}
|
|
|
|
this.sendResult(id, result);
|
|
|
|
// Session clean up.
|
|
if (module === "session" && command === "end") {
|
|
this.endSession();
|
|
}
|
|
// Close the browser.
|
|
// TODO Bug 1842018. Refactor this part to return the response
|
|
// when the quitting of the browser is finished.
|
|
else if (module === "browser" && command === "close") {
|
|
// Register handler to run WebDriver BiDi specific shutdown code.
|
|
Services.obs.addObserver(this, "quit-application-requested");
|
|
|
|
// TODO Bug 1836282. Add as the third argument "moz:windowless" capability
|
|
// from the session, when this capability is supported by Webdriver BiDi.
|
|
await lazy.quit(["eForceQuit"], false);
|
|
|
|
Services.obs.removeObserver(this, "quit-application-requested");
|
|
}
|
|
} catch (e) {
|
|
this.sendError(id, e);
|
|
}
|
|
|
|
if (Services.profiler?.IsActive()) {
|
|
ChromeUtils.addProfilerMarker(
|
|
"BiDi: Command",
|
|
{ startTime, category: "Remote-Protocol" },
|
|
`${method} (${id})`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Splits a WebDriver BiDi method into module and command components.
|
|
*
|
|
* @param {string} method
|
|
* Name of the method to split, e.g. "session.subscribe".
|
|
*
|
|
* @returns {Record<string, string>}
|
|
* Object with the module ("session") and command ("subscribe")
|
|
* as properties.
|
|
*/
|
|
export function splitMethod(method) {
|
|
const parts = method.split(".");
|
|
|
|
if (parts.length != 2 || !parts[0].length || !parts[1].length) {
|
|
throw new TypeError(`Invalid method format: '${method}'`);
|
|
}
|
|
|
|
return {
|
|
module: parts[0],
|
|
command: parts[1],
|
|
};
|
|
}
|