summaryrefslogtreecommitdiffstats
path: root/remote/cdp/sessions
diff options
context:
space:
mode:
Diffstat (limited to 'remote/cdp/sessions')
-rw-r--r--remote/cdp/sessions/ContentProcessSession.sys.mjs104
-rw-r--r--remote/cdp/sessions/MainProcessSession.sys.mjs12
-rw-r--r--remote/cdp/sessions/Session.sys.mjs80
-rw-r--r--remote/cdp/sessions/TabSession.sys.mjs112
-rw-r--r--remote/cdp/sessions/frame-script.js13
5 files changed, 321 insertions, 0 deletions
diff --git a/remote/cdp/sessions/ContentProcessSession.sys.mjs b/remote/cdp/sessions/ContentProcessSession.sys.mjs
new file mode 100644
index 0000000000..d7aa3de57b
--- /dev/null
+++ b/remote/cdp/sessions/ContentProcessSession.sys.mjs
@@ -0,0 +1,104 @@
+/* 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/. */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ ContentProcessDomains:
+ "chrome://remote/content/cdp/domains/ContentProcessDomains.sys.mjs",
+ ContextObserver:
+ "chrome://remote/content/cdp/observers/ContextObserver.sys.mjs",
+ DomainCache: "chrome://remote/content/cdp/domains/DomainCache.sys.mjs",
+});
+
+export class ContentProcessSession {
+ constructor(messageManager, browsingContext, content, docShell) {
+ this.messageManager = messageManager;
+ this.browsingContext = browsingContext;
+ this.content = content;
+ this.docShell = docShell;
+ // Most children or sibling classes are going to assume that docShell
+ // implements the following interface. So do the QI only once from here.
+ this.docShell.QueryInterface(Ci.nsIWebNavigation);
+
+ this.domains = new lazy.DomainCache(this, lazy.ContentProcessDomains);
+ this.messageManager.addMessageListener("remote:request", this);
+ this.messageManager.addMessageListener("remote:destroy", this);
+ }
+
+ destructor() {
+ this._contextObserver?.destructor();
+
+ this.messageManager.removeMessageListener("remote:request", this);
+ this.messageManager.removeMessageListener("remote:destroy", this);
+ this.domains.clear();
+ }
+
+ get contextObserver() {
+ if (!this._contextObserver) {
+ this._contextObserver = new lazy.ContextObserver(
+ this.docShell.chromeEventHandler
+ );
+ }
+ return this._contextObserver;
+ }
+
+ // Domain event listener
+
+ onEvent(eventName, params) {
+ this.messageManager.sendAsyncMessage("remote:event", {
+ browsingContextId: this.browsingContext.id,
+ event: {
+ eventName,
+ params,
+ },
+ });
+ }
+
+ // nsIMessageListener
+
+ async receiveMessage({ name, data }) {
+ const { browsingContextId } = data;
+
+ // We may have more than one tab loaded in the same process,
+ // and debug the two at the same time. We want to ensure not
+ // mixing up the requests made against two such tabs.
+ // Each tab is going to have its own frame script instance
+ // and two communication channels are going to be set up via
+ // the two message managers.
+ if (browsingContextId != this.browsingContext.id) {
+ return;
+ }
+
+ switch (name) {
+ case "remote:request":
+ try {
+ const { id, domain, command, params } = data.request;
+
+ const result = await this.domains.execute(domain, command, params);
+
+ this.messageManager.sendAsyncMessage("remote:result", {
+ browsingContextId,
+ id,
+ result,
+ });
+ } catch (e) {
+ this.messageManager.sendAsyncMessage("remote:error", {
+ browsingContextId,
+ id: data.request.id,
+ error: {
+ name: e.name || "exception",
+ message: e.message || String(e),
+ stack: e.stack,
+ },
+ });
+ }
+ break;
+
+ case "remote:destroy":
+ this.destructor();
+ break;
+ }
+ }
+}
diff --git a/remote/cdp/sessions/MainProcessSession.sys.mjs b/remote/cdp/sessions/MainProcessSession.sys.mjs
new file mode 100644
index 0000000000..259e1312ac
--- /dev/null
+++ b/remote/cdp/sessions/MainProcessSession.sys.mjs
@@ -0,0 +1,12 @@
+/* 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 { Session } from "chrome://remote/content/cdp/sessions/Session.sys.mjs";
+
+/**
+ * A session, dedicated to the main process target.
+ * For some reason, it doesn't need any specific code and can share the base Session class
+ * aside TabSession.
+ */
+export class MainProcessSession extends Session {}
diff --git a/remote/cdp/sessions/Session.sys.mjs b/remote/cdp/sessions/Session.sys.mjs
new file mode 100644
index 0000000000..f1d066ce77
--- /dev/null
+++ b/remote/cdp/sessions/Session.sys.mjs
@@ -0,0 +1,80 @@
+/* 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/. */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ DomainCache: "chrome://remote/content/cdp/domains/DomainCache.sys.mjs",
+ NetworkObserver:
+ "chrome://remote/content/cdp/observers/NetworkObserver.sys.mjs",
+ ParentProcessDomains:
+ "chrome://remote/content/cdp/domains/ParentProcessDomains.sys.mjs",
+});
+
+/**
+ * A session represents exactly one client WebSocket connection.
+ *
+ * Every new WebSocket connections is associated with one session that
+ * deals with dispatching incoming command requests to the right
+ * target, sending back responses, and propagating events originating
+ * from domains.
+ * Then, some subsequent Sessions may be created over a single WebSocket
+ * connection. In this case, the subsequent session will have an `id`
+ * being passed to their constructor and each packet of these sessions
+ * will have a `sessionId` attribute in order to filter the packets
+ * by session both on client and server side.
+ */
+export class Session {
+ /**
+ * @param Connection connection
+ * The connection used to communicate with the server.
+ * @param Target target
+ * The target to which this session communicates with.
+ * @param Number id (optional)
+ * If this session isn't the default one used for the HTTP endpoint we
+ * connected to, the session requires an id to distinguish it from the default
+ * one. This id is used to filter our request, responses and events between
+ * all active sessions. For now, this is only passed by `Target.attachToTarget()`.
+ */
+ constructor(connection, target, id) {
+ this.connection = connection;
+ this.target = target;
+ this.id = id;
+
+ this.domains = new lazy.DomainCache(this, lazy.ParentProcessDomains);
+ }
+
+ destructor() {
+ if (
+ this.networkObserver &&
+ this.networkObserver.isActive(this.target.browser)
+ ) {
+ this.networkObserver.dispose();
+ }
+ this.domains.clear();
+ }
+
+ execute(id, domain, command, params) {
+ return this.domains.execute(domain, command, params);
+ }
+
+ get networkObserver() {
+ if (!this._networkObserver) {
+ this._networkObserver = new lazy.NetworkObserver();
+ }
+ return this._networkObserver;
+ }
+
+ /**
+ * Domains event listener. Called when an event is fired
+ * by any Domain and has to be sent to the client.
+ */
+ onEvent(eventName, params) {
+ this.connection.sendEvent(eventName, params, this.id);
+ }
+
+ toString() {
+ return `[object ${this.constructor.name} ${this.connection.id}]`;
+ }
+}
diff --git a/remote/cdp/sessions/TabSession.sys.mjs b/remote/cdp/sessions/TabSession.sys.mjs
new file mode 100644
index 0000000000..a6b9fbf83a
--- /dev/null
+++ b/remote/cdp/sessions/TabSession.sys.mjs
@@ -0,0 +1,112 @@
+/* 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 { Session } from "chrome://remote/content/cdp/sessions/Session.sys.mjs";
+
+/**
+ * A session to communicate with a given tab
+ */
+export class TabSession extends Session {
+ /**
+ * @param Connection connection
+ * The connection used to communicate with the server.
+ * @param TabTarget target
+ * The tab target to which this session communicates with.
+ * @param Number id (optional)
+ * If this session isn't the default one used for the HTTP endpoint we
+ * connected to, the session requires an id to distinguish it from the default
+ * one. This id is used to filter our request, responses and events between
+ * all active sessions.
+ * For now, this is only passed by `Target.attachToTarget()`.
+ * Otherwise it will be undefined when you are connecting directly to
+ * a given Tab. i.e. connect directly to the WebSocket URL provided by
+ * /json/list HTTP endpoint.
+ */
+ constructor(connection, target, id) {
+ super(connection, target, id);
+
+ // Request id => { resolve, reject }
+ this.requestPromises = new Map();
+
+ this.mm.addMessageListener("remote:event", this);
+ this.mm.addMessageListener("remote:result", this);
+ this.mm.addMessageListener("remote:error", this);
+
+ this.mm.loadFrameScript(
+ "chrome://remote/content/cdp/sessions/frame-script.js",
+ false
+ );
+ }
+
+ destructor() {
+ super.destructor();
+
+ this.requestPromises.clear();
+
+ // this.mm might be null if the browser of the TabTarget was already closed.
+ // See Bug 1747301.
+ this.mm?.sendAsyncMessage("remote:destroy", {
+ browsingContextId: this.browsingContext.id,
+ });
+
+ this.mm?.removeMessageListener("remote:event", this);
+ this.mm?.removeMessageListener("remote:result", this);
+ this.mm?.removeMessageListener("remote:error", this);
+ }
+
+ execute(id, domain, command, params) {
+ // Check if the domain and command is implemented in the parent
+ // and execute it there. Otherwise forward the command to the content process
+ // in order to try to execute it in the content process.
+ if (this.domains.domainSupportsMethod(domain, command)) {
+ return super.execute(id, domain, command, params);
+ }
+ return this.executeInChild(id, domain, command, params);
+ }
+
+ executeInChild(id, domain, command, params) {
+ return new Promise((resolve, reject) => {
+ // Save the promise's resolution and rejection handler in order to later
+ // resolve this promise once we receive the reply back from the content process.
+ this.requestPromises.set(id, { resolve, reject });
+
+ this.mm.sendAsyncMessage("remote:request", {
+ browsingContextId: this.browsingContext.id,
+ request: { id, domain, command, params },
+ });
+ });
+ }
+
+ get mm() {
+ return this.target.mm;
+ }
+
+ get browsingContext() {
+ return this.target.browsingContext;
+ }
+
+ // nsIMessageListener
+
+ receiveMessage({ name, data }) {
+ const { id, result, event, error } = data;
+
+ switch (name) {
+ case "remote:result":
+ const { resolve } = this.requestPromises.get(id);
+ resolve(result);
+ this.requestPromises.delete(id);
+ break;
+
+ case "remote:event":
+ this.connection.sendEvent(event.eventName, event.params, this.id);
+ break;
+
+ case "remote:error":
+ const { reject } = this.requestPromises.get(id);
+ reject(error);
+ this.requestPromises.delete(id);
+ break;
+ }
+ }
+}
diff --git a/remote/cdp/sessions/frame-script.js b/remote/cdp/sessions/frame-script.js
new file mode 100644
index 0000000000..88071086cb
--- /dev/null
+++ b/remote/cdp/sessions/frame-script.js
@@ -0,0 +1,13 @@
+/* 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/. */
+
+/* eslint-env mozilla/frame-script */
+
+"use strict";
+
+const { ContentProcessSession } = ChromeUtils.importESModule(
+ "chrome://remote/content/cdp/sessions/ContentProcessSession.sys.mjs"
+);
+
+new ContentProcessSession(this, docShell.browsingContext, content, docShell);