diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /remote/targets | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/targets')
-rw-r--r-- | remote/targets/MainProcessTarget.jsm | 59 | ||||
-rw-r--r-- | remote/targets/TabTarget.jsm | 164 | ||||
-rw-r--r-- | remote/targets/Target.jsm | 74 | ||||
-rw-r--r-- | remote/targets/TargetList.jsm | 173 |
4 files changed, 470 insertions, 0 deletions
diff --git a/remote/targets/MainProcessTarget.jsm b/remote/targets/MainProcessTarget.jsm new file mode 100644 index 0000000000..f723edc93a --- /dev/null +++ b/remote/targets/MainProcessTarget.jsm @@ -0,0 +1,59 @@ +/* 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 EXPORTED_SYMBOLS = ["MainProcessTarget"]; + +const { Target } = ChromeUtils.import( + "chrome://remote/content/targets/Target.jsm" +); +const { MainProcessSession } = ChromeUtils.import( + "chrome://remote/content/sessions/MainProcessSession.jsm" +); +const { RemoteAgent } = ChromeUtils.import( + "chrome://remote/content/RemoteAgent.jsm" +); + +/** + * 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 + */ +class MainProcessTarget extends Target { + /* + * @param Targets targets + */ + constructor(targets) { + super(targets, MainProcessSession); + + this.type = "browser"; + + // Define the HTTP path to query this target + this.path = `/devtools/browser/${this.id}`; + } + + get wsDebuggerURL() { + const { host, port } = 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/targets/TabTarget.jsm b/remote/targets/TabTarget.jsm new file mode 100644 index 0000000000..fd672677aa --- /dev/null +++ b/remote/targets/TabTarget.jsm @@ -0,0 +1,164 @@ +/* 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 EXPORTED_SYMBOLS = ["TabTarget"]; + +const { Target } = ChromeUtils.import( + "chrome://remote/content/targets/Target.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { TabSession } = ChromeUtils.import( + "chrome://remote/content/sessions/TabSession.jsm" +); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { RemoteAgent } = ChromeUtils.import( + "chrome://remote/content/RemoteAgent.jsm" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "Favicons", + "@mozilla.org/browser/favicon-service;1", + "nsIFaviconService" +); + +/** + * Target for a local tab or a remoted frame. + */ +class TabTarget extends Target { + /** + * @param Targets targets + * @param BrowserElement browser + */ + constructor(targets, browser) { + super(targets, TabSession); + + this.browser = 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. + * + * @return {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; + } + + /** @return {Promise.<String=>} */ + get faviconUrl() { + return new Promise((resolve, reject) => { + 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 } = 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, + browsingContextId: this.browsingContext.id, + 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/targets/Target.jsm b/remote/targets/Target.jsm new file mode 100644 index 0000000000..b413b4b220 --- /dev/null +++ b/remote/targets/Target.jsm @@ -0,0 +1,74 @@ +/* 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 EXPORTED_SYMBOLS = ["Target"]; + +const { Connection } = ChromeUtils.import( + "chrome://remote/content/Connection.jsm" +); +const { WebSocketTransport } = ChromeUtils.import( + "chrome://remote/content/server/WebSocketTransport.jsm" +); +const { WebSocketHandshake } = ChromeUtils.import( + "chrome://remote/content/server/WebSocketHandshake.jsm" +); + +const UUIDGen = Cc["@mozilla.org/uuid-generator;1"].getService( + Ci.nsIUUIDGenerator +); + +/** + * Base class for all the Targets. + */ +class Target { + /** + * @param Targets targets + * @param Class sessionClass + */ + constructor(targets, sessionClass) { + // Save a reference to Targets instance in order to expose it to: + // domains/parent/Target.jsm + this.targets = targets; + + // 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 = UUIDGen.generateUUID() + .toString() + .slice(1, -1); + } + + /** + * Close all active connections made to this target. + */ + destructor() { + for (const conn of this.connections) { + conn.close(); + } + } + + // nsIHttpRequestHandler + + async handle(request, response) { + const so = await WebSocketHandshake.upgrade(request, response); + const transport = new WebSocketTransport(so); + const conn = new Connection(transport, 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/targets/TargetList.jsm b/remote/targets/TargetList.jsm new file mode 100644 index 0000000000..7117fbbee4 --- /dev/null +++ b/remote/targets/TargetList.jsm @@ -0,0 +1,173 @@ +/* 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 EXPORTED_SYMBOLS = ["TargetList"]; + +const { EventEmitter } = ChromeUtils.import( + "resource://gre/modules/EventEmitter.jsm" +); +const { TabTarget } = ChromeUtils.import( + "chrome://remote/content/targets/TabTarget.jsm" +); +const { MainProcessTarget } = ChromeUtils.import( + "chrome://remote/content/targets/MainProcessTarget.jsm" +); +const { TabObserver } = ChromeUtils.import( + "chrome://remote/content/observers/TargetObserver.jsm" +); + +class TargetList { + constructor() { + // Target ID -> Target + this._targets = new Map(); + + 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 TabObserver({ registerExisting: true }); + this.tabObserver.on("open", async (eventName, tab) => { + const target = new TabTarget(this, tab.linkedBrowser); + this.registerTarget(target); + }); + this.tabObserver.on("close", (eventName, tab) => { + const browser = tab.linkedBrowser; + // Ignore the browsers that haven't had time to initialize. + if (!browser.browsingContext) { + return; + } + const target = this.getByBrowsingContext(browser.browsingContext.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 + * + * @return {Target} + */ + getById(id) { + return this._targets.get(id); + } + + /** + * Get Target instance by browsing context id + * + * @param {number} id + * browsing context id + * + * @return {Target} + */ + getByBrowsingContext(id) { + let rv; + for (const target of this._targets.values()) { + if (target.browsingContext && target.browsingContext.id === id) { + rv = target; + break; + } + } + return rv; + } + + /** + * 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 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}]`; + } +} |