summaryrefslogtreecommitdiffstats
path: root/remote/cdp/targets
diff options
context:
space:
mode:
Diffstat (limited to 'remote/cdp/targets')
-rw-r--r--remote/cdp/targets/MainProcessTarget.sys.mjs55
-rw-r--r--remote/cdp/targets/TabTarget.sys.mjs161
-rw-r--r--remote/cdp/targets/Target.sys.mjs62
-rw-r--r--remote/cdp/targets/TargetList.sys.mjs159
4 files changed, 437 insertions, 0 deletions
diff --git a/remote/cdp/targets/MainProcessTarget.sys.mjs b/remote/cdp/targets/MainProcessTarget.sys.mjs
new file mode 100644
index 0000000000..1f32b5bde5
--- /dev/null
+++ b/remote/cdp/targets/MainProcessTarget.sys.mjs
@@ -0,0 +1,55 @@
+/* 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 { Target } from "chrome://remote/content/cdp/targets/Target.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ MainProcessSession:
+ "chrome://remote/content/cdp/sessions/MainProcessSession.sys.mjs",
+ RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
+});
+
+/**
+ * The main process Target.
+ *
+ * Matches BrowserDevToolsAgentHost from chromium, and only support a couple of Domains:
+ * https://cs.chromium.org/chromium/src/content/browser/devtools/browser_devtools_agent_host.cc?dr=CSs&g=0&l=80-91
+ */
+export class MainProcessTarget extends Target {
+ /*
+ * @param TargetList targetList
+ */
+ constructor(targetList) {
+ super(targetList, lazy.MainProcessSession);
+
+ this.type = "browser";
+
+ // Define the HTTP path to query this target
+ this.path = `/devtools/browser/${this.id}`;
+ }
+
+ get wsDebuggerURL() {
+ const { host, port } = lazy.RemoteAgent;
+ return `ws://${host}:${port}${this.path}`;
+ }
+
+ toString() {
+ return `[object MainProcessTarget]`;
+ }
+
+ toJSON() {
+ return {
+ description: "Main process target",
+ devtoolsFrontendUrl: "",
+ faviconUrl: "",
+ id: this.id,
+ title: "Main process target",
+ type: this.type,
+ url: "",
+ webSocketDebuggerUrl: this.wsDebuggerURL,
+ };
+ }
+}
diff --git a/remote/cdp/targets/TabTarget.sys.mjs b/remote/cdp/targets/TabTarget.sys.mjs
new file mode 100644
index 0000000000..a35b0c3f07
--- /dev/null
+++ b/remote/cdp/targets/TabTarget.sys.mjs
@@ -0,0 +1,161 @@
+/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+import { Target } from "chrome://remote/content/cdp/targets/Target.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
+ TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
+ TabSession: "chrome://remote/content/cdp/sessions/TabSession.sys.mjs",
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "Favicons",
+ "@mozilla.org/browser/favicon-service;1",
+ "nsIFaviconService"
+);
+
+/**
+ * Target for a local tab or a remoted frame.
+ */
+export class TabTarget extends Target {
+ /**
+ * @param {TargetList} targetList
+ * @param {BrowserElement} browser
+ */
+ constructor(targetList, browser) {
+ super(targetList, lazy.TabSession);
+
+ this.browser = browser;
+
+ // The tab target uses a unique id as shared with WebDriver to reference
+ // a specific tab.
+ this.id = lazy.TabManager.getIdForBrowser(browser);
+
+ // Define the HTTP path to query this target
+ this.path = `/devtools/page/${this.id}`;
+
+ Services.obs.addObserver(this, "message-manager-disconnect");
+ }
+
+ destructor() {
+ Services.obs.removeObserver(this, "message-manager-disconnect");
+ super.destructor();
+ }
+
+ get browserContextId() {
+ return parseInt(this.browser.getAttribute("usercontextid"));
+ }
+
+ get browsingContext() {
+ return this.browser.browsingContext;
+ }
+
+ get mm() {
+ return this.browser.messageManager;
+ }
+
+ get window() {
+ return this.browser.ownerGlobal;
+ }
+
+ get tab() {
+ return this.window.gBrowser.getTabForBrowser(this.browser);
+ }
+
+ /**
+ * Determines if the content browser remains attached
+ * to its parent chrome window.
+ *
+ * We determine this by checking if the <browser> element
+ * is still attached to the DOM.
+ *
+ * @returns {boolean}
+ * True if target's browser is still attached,
+ * false if it has been disconnected.
+ */
+ get closed() {
+ return !this.browser || !this.browser.isConnected;
+ }
+
+ get description() {
+ return "";
+ }
+
+ get frontendURL() {
+ return null;
+ }
+
+ /** @returns {Promise<string|null>} */
+ get faviconUrl() {
+ return new Promise((resolve, reject) => {
+ lazy.Favicons.getFaviconURLForPage(this.browser.currentURI, url => {
+ if (url) {
+ resolve(url.spec);
+ } else {
+ resolve(null);
+ }
+ });
+ });
+ }
+
+ get title() {
+ return this.browsingContext.currentWindowGlobal.documentTitle;
+ }
+
+ get type() {
+ return "page";
+ }
+
+ get url() {
+ return this.browser.currentURI.spec;
+ }
+
+ get wsDebuggerURL() {
+ const { host, port } = lazy.RemoteAgent;
+ return `ws://${host}:${port}${this.path}`;
+ }
+
+ toString() {
+ return `[object Target ${this.id}]`;
+ }
+
+ toJSON() {
+ return {
+ description: this.description,
+ devtoolsFrontendUrl: this.frontendURL,
+ // TODO(ato): toJSON cannot be marked async )-:
+ faviconUrl: "",
+ id: this.id,
+ // Bug 1680817: Fails to encode some UTF-8 characters
+ // title: this.title,
+ type: this.type,
+ url: this.url,
+ webSocketDebuggerUrl: this.wsDebuggerURL,
+ };
+ }
+
+ // nsIObserver
+
+ observe(subject, topic, data) {
+ if (subject === this.mm && subject == "message-manager-disconnect") {
+ // disconnect debugging target if <browser> is disconnected,
+ // otherwise this is just a host process change
+ if (this.closed) {
+ this.disconnect();
+ }
+ }
+ }
+
+ // XPCOM
+
+ get QueryInterface() {
+ return ChromeUtils.generateQI(["nsIHttpRequestHandler", "nsIObserver"]);
+ }
+}
diff --git a/remote/cdp/targets/Target.sys.mjs b/remote/cdp/targets/Target.sys.mjs
new file mode 100644
index 0000000000..9264110c37
--- /dev/null
+++ b/remote/cdp/targets/Target.sys.mjs
@@ -0,0 +1,62 @@
+/* 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, {
+ CDPConnection: "chrome://remote/content/cdp/CDPConnection.sys.mjs",
+ generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
+ WebSocketHandshake:
+ "chrome://remote/content/server/WebSocketHandshake.sys.mjs",
+});
+
+/**
+ * Base class for all the targets.
+ */
+export class Target {
+ /**
+ * @param {TargetList} targetList
+ * @param {Class} sessionClass
+ */
+ constructor(targetList, sessionClass) {
+ // Save a reference to TargetList instance in order to expose it to:
+ // domains/parent/Target.jsm
+ this.targetList = targetList;
+
+ // When a new connection is made to this target,
+ // we will instantiate a new session based on this given class.
+ // The session class is specific to each target's kind and is passed
+ // by the inheriting class.
+ this.sessionClass = sessionClass;
+
+ // There can be more than one connection if multiple clients connect to the remote agent.
+ this.connections = new Set();
+ this.id = lazy.generateUUID();
+ }
+
+ /**
+ * Close all active connections made to this target.
+ */
+ destructor() {
+ for (const conn of this.connections) {
+ conn.close();
+ }
+ }
+
+ // nsIHttpRequestHandler
+
+ async handle(request, response) {
+ const webSocket = await lazy.WebSocketHandshake.upgrade(request, response);
+ const conn = new lazy.CDPConnection(webSocket, response._connection);
+ const session = new this.sessionClass(conn, this);
+ conn.registerSession(session);
+ this.connections.add(conn);
+ }
+
+ // XPCOM
+
+ get QueryInterface() {
+ return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
+ }
+}
diff --git a/remote/cdp/targets/TargetList.sys.mjs b/remote/cdp/targets/TargetList.sys.mjs
new file mode 100644
index 0000000000..a68b36763f
--- /dev/null
+++ b/remote/cdp/targets/TargetList.sys.mjs
@@ -0,0 +1,159 @@
+/* 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, {
+ EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
+
+ MainProcessTarget:
+ "chrome://remote/content/cdp/targets/MainProcessTarget.sys.mjs",
+ TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
+ TabObserver: "chrome://remote/content/cdp/observers/TargetObserver.sys.mjs",
+ TabTarget: "chrome://remote/content/cdp/targets/TabTarget.sys.mjs",
+});
+
+export class TargetList {
+ constructor() {
+ // Target ID -> Target
+ this._targets = new Map();
+
+ lazy.EventEmitter.decorate(this);
+ }
+
+ /**
+ * Start listing and listening for all the debuggable targets
+ */
+ async watchForTargets() {
+ await this.watchForTabs();
+ }
+
+ unwatchForTargets() {
+ this.unwatchForTabs();
+ }
+
+ /**
+ * Watch for all existing and new tabs being opened.
+ * So that we can create the related TabTarget instance for
+ * each of them.
+ */
+ async watchForTabs() {
+ if (this.tabObserver) {
+ throw new Error("Targets is already watching for new tabs");
+ }
+
+ this.tabObserver = new lazy.TabObserver({ registerExisting: true });
+
+ // Handle creation of tab targets for opened tabs.
+ this.tabObserver.on("open", async (eventName, tab) => {
+ const target = new lazy.TabTarget(this, tab.linkedBrowser);
+ this.registerTarget(target);
+ });
+
+ // Handle removal of tab targets when tabs are closed.
+ this.tabObserver.on("close", (eventName, tab) => {
+ const browser = tab.linkedBrowser;
+
+ // Ignore unloaded tabs.
+ if (browser.browserId === 0) {
+ return;
+ }
+
+ const id = lazy.TabManager.getIdForBrowser(browser);
+ const target = Array.from(this._targets.values()).find(
+ target => target.id == id
+ );
+ if (target) {
+ this.destroyTarget(target);
+ }
+ });
+ await this.tabObserver.start();
+ }
+
+ unwatchForTabs() {
+ if (this.tabObserver) {
+ this.tabObserver.stop();
+ this.tabObserver = null;
+ }
+ }
+
+ /**
+ * To be called right after instantiating a new Target instance.
+ * This will hold the new instance in the list and notify about
+ * its creation.
+ */
+ registerTarget(target) {
+ this._targets.set(target.id, target);
+ this.emit("target-created", target);
+ }
+
+ /**
+ * To be called when the debuggable target has been destroy.
+ * So that we can notify it no longer exists and disconnect
+ * all connecting made to debug it.
+ */
+ destroyTarget(target) {
+ target.destructor();
+ this._targets.delete(target.id);
+ this.emit("target-destroyed", target);
+ }
+
+ /**
+ * Destroy all the registered target of all kinds.
+ * This will end up dropping all connections made to debug any of them.
+ */
+ destructor() {
+ for (const target of this) {
+ this.destroyTarget(target);
+ }
+ this._targets.clear();
+ if (this.mainProcessTarget) {
+ this.mainProcessTarget = null;
+ }
+
+ this.unwatchForTargets();
+ }
+
+ get size() {
+ return this._targets.size;
+ }
+
+ /**
+ * Get Target instance by target id
+ *
+ * @param {string} id
+ * Target id
+ *
+ * @returns {Target}
+ */
+ getById(id) {
+ return this._targets.get(id);
+ }
+
+ /**
+ * Get the Target instance for the main process.
+ * This target is a singleton and only exposes a subset of domains.
+ */
+ getMainProcessTarget() {
+ if (!this.mainProcessTarget) {
+ this.mainProcessTarget = new lazy.MainProcessTarget(this);
+ this.registerTarget(this.mainProcessTarget);
+ }
+ return this.mainProcessTarget;
+ }
+
+ *[Symbol.iterator]() {
+ for (const target of this._targets.values()) {
+ yield target;
+ }
+ }
+
+ toJSON() {
+ return [...this];
+ }
+
+ toString() {
+ return `[object TargetList ${this.size}]`;
+ }
+}