diff options
Diffstat (limited to 'remote/cdp/CDP.sys.mjs')
-rw-r--r-- | remote/cdp/CDP.sys.mjs | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/remote/cdp/CDP.sys.mjs b/remote/cdp/CDP.sys.mjs new file mode 100644 index 0000000000..0307c71149 --- /dev/null +++ b/remote/cdp/CDP.sys.mjs @@ -0,0 +1,145 @@ +/* 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, { + JSONHandler: "chrome://remote/content/cdp/JSONHandler.sys.mjs", + Log: "chrome://remote/content/shared/Log.sys.mjs", + RecommendedPreferences: + "chrome://remote/content/shared/RecommendedPreferences.sys.mjs", + TargetList: "chrome://remote/content/cdp/targets/TargetList.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.CDP) +); +ChromeUtils.defineLazyGetter(lazy, "textEncoder", () => new TextEncoder()); + +// Map of CDP-specific preferences that should be set via +// RecommendedPreferences. +const RECOMMENDED_PREFS = new Map([ + // Prevent various error message on the console + // jest-puppeteer asserts that no error message is emitted by the console + [ + "browser.contentblocking.features.standard", + "-tp,tpPrivate,cookieBehavior0,-cm,-fp", + ], + // Accept all cookies (see behavior definitions in nsICookieService.idl) + ["network.cookie.cookieBehavior", 0], +]); + +/** + * Entry class for the Chrome DevTools Protocol support. + * + * It holds the list of available targets (tabs, main browser), and also + * sets up the necessary handlers for the HTTP server. + * + * @see https://chromedevtools.github.io/devtools-protocol + */ +export class CDP { + /** + * Creates a new instance of the CDP class. + * + * @param {RemoteAgent} agent + * Reference to the Remote Agent instance. + */ + constructor(agent) { + this.agent = agent; + this.targetList = null; + + this._running = false; + this._activePortPath; + } + + get address() { + const mainTarget = this.targetList.getMainProcessTarget(); + return mainTarget.wsDebuggerURL; + } + + get mainTargetPath() { + const mainTarget = this.targetList.getMainProcessTarget(); + return mainTarget.path; + } + + /** + * Starts the CDP support. + */ + async start() { + if (this._running) { + return; + } + + // Note: Ideally this would only be set at the end of the method. However + // since start() is async, we prefer to set the flag early in order to + // avoid potential race conditions. + this._running = true; + + lazy.RecommendedPreferences.applyPreferences(RECOMMENDED_PREFS); + + // Starting CDP too early can cause issues with clients in not being able + // to find any available target. Also when closing the application while + // it's still starting up can cause shutdown hangs. As such CDP will be + // started when the initial application window has finished initializing. + lazy.logger.debug(`Waiting for initial application window`); + await this.agent.browserStartupFinished; + + this.agent.server.registerPrefixHandler("/", new lazy.JSONHandler(this)); + + this.targetList = new lazy.TargetList(); + this.targetList.on("target-created", (eventName, target) => { + this.agent.server.registerPathHandler(target.path, target); + }); + this.targetList.on("target-destroyed", (eventName, target) => { + this.agent.server.registerPathHandler(target.path, null); + }); + + await this.targetList.watchForTargets(); + + Cu.printStderr(`DevTools listening on ${this.address}\n`); + + // Write connection details to DevToolsActivePort file within the profile. + this._activePortPath = PathUtils.join( + PathUtils.profileDir, + "DevToolsActivePort" + ); + + const data = `${this.agent.port}\n${this.mainTargetPath}`; + try { + await IOUtils.write(this._activePortPath, lazy.textEncoder.encode(data)); + } catch (e) { + lazy.logger.warn( + `Failed to create ${this._activePortPath} (${e.message})` + ); + } + } + + /** + * Stops the CDP support. + */ + async stop() { + if (!this._running) { + return; + } + + try { + await IOUtils.remove(this._activePortPath); + } catch (e) { + lazy.logger.warn( + `Failed to remove ${this._activePortPath} (${e.message})` + ); + } + + try { + this.targetList?.destructor(); + this.targetList = null; + + lazy.RecommendedPreferences.restorePreferences(RECOMMENDED_PREFS); + } catch (e) { + lazy.logger.error("Failed to stop protocol", e); + } finally { + this._running = false; + } + } +} |