From 72981bee50094a26679abce77ca66e03f14a9785 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Wed, 11 Sep 2024 15:08:36 +0000 Subject: [PATCH] Bug 1916766 - Centralise HiddenBrowserManager into HiddenFrame.sys.mjs and make it a singleton. r=mossop Differential Revision: https://phabricator.services.mozilla.com/D221061 --- .../pagedata/PageDataService.sys.mjs | 98 +----------------- .../UserCharacteristicsPageService.sys.mjs | 99 +------------------ toolkit/modules/HiddenFrame.sys.mjs | 95 ++++++++++++++++++ 3 files changed, 99 insertions(+), 193 deletions(-) diff --git a/browser/components/pagedata/PageDataService.sys.mjs b/browser/components/pagedata/PageDataService.sys.mjs index 3cc93ead39b7..7eaf49e7ab53 100644 --- a/browser/components/pagedata/PageDataService.sys.mjs +++ b/browser/components/pagedata/PageDataService.sys.mjs @@ -10,7 +10,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", - HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs", + HiddenBrowserManager: "resource://gre/modules/HiddenFrame.sys.mjs", }); ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { @@ -35,9 +35,6 @@ XPCOMUtils.defineLazyPreferenceGetter( const ALLOWED_SCHEMES = ["http", "https", "data", "blob"]; -const BACKGROUND_WIDTH = 1024; -const BACKGROUND_HEIGHT = 768; - /** * Shifts the first element out of the set. * @@ -58,90 +55,6 @@ function shift(set) { return value; } -/** - * A manager for hidden browsers. Responsible for creating and destroying a - * hidden frame to hold them. - */ -class HiddenBrowserManager { - /** - * The hidden frame if one has been created. - * - * @type {HiddenFrame | null} - */ - #frame = null; - /** - * The number of hidden browser elements currently in use. - * - * @type {number} - */ - #browsers = 0; - - /** - * Creates and returns a new hidden browser. - * - * @returns {Browser} - */ - async #acquireBrowser() { - this.#browsers++; - if (!this.#frame) { - this.#frame = new lazy.HiddenFrame(); - } - - let frame = await this.#frame.get(); - let doc = frame.document; - let browser = doc.createXULElement("browser"); - browser.setAttribute("remote", "true"); - browser.setAttribute("type", "content"); - browser.setAttribute( - "style", - ` - width: ${BACKGROUND_WIDTH}px; - min-width: ${BACKGROUND_WIDTH}px; - height: ${BACKGROUND_HEIGHT}px; - min-height: ${BACKGROUND_HEIGHT}px; - ` - ); - browser.setAttribute("maychangeremoteness", "true"); - doc.documentElement.appendChild(browser); - - return browser; - } - - /** - * Releases the given hidden browser. - * - * @param {Browser} browser - * The hidden browser element. - */ - #releaseBrowser(browser) { - browser.remove(); - - this.#browsers--; - if (this.#browsers == 0) { - this.#frame.destroy(); - this.#frame = null; - } - } - - /** - * Calls a callback function with a new hidden browser. - * This function will return whatever the callback function returns. - * - * @param {Callback} callback - * The callback function will be called with the browser element and may - * be asynchronous. - * @returns {T} - */ - async withHiddenBrowser(callback) { - let browser = await this.#acquireBrowser(); - try { - return await callback(browser); - } finally { - this.#releaseBrowser(browser); - } - } -} - /** * @typedef {object} CacheEntry * An entry in the page data cache. @@ -293,13 +206,6 @@ export const PageDataService = new (class PageDataService extends EventEmitter { */ #userIsIdle = false; - /** - * A manager for hidden browsers. - * - * @type {HiddenBrowserManager} - */ - #browserManager = new HiddenBrowserManager(); - /** * A map of hidden browsers to a resolve function that should be passed the * actor that was created for the browser. @@ -535,7 +441,7 @@ export const PageDataService = new (class PageDataService extends EventEmitter { * Resolves to the found pagedata or null in case of error. */ async fetchPageData(url) { - return this.#browserManager.withHiddenBrowser(async browser => { + return lazy.HiddenBrowserManager.withHiddenBrowser(async browser => { try { let { promise, resolve } = Promise.withResolvers(); this.#backgroundBrowsers.set(browser, resolve); diff --git a/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs b/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs index 34634cdcd3b8..bf2ba2636d69 100644 --- a/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs +++ b/toolkit/components/resistfingerprinting/UserCharacteristicsPageService.sys.mjs @@ -6,7 +6,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs", + HiddenBrowserManager: "resource://gre/modules/HiddenFrame.sys.mjs", Preferences: "resource://gre/modules/Preferences.sys.mjs", setTimeout: "resource://gre/modules/Timer.sys.mjs", clearTimeout: "resource://gre/modules/Timer.sys.mjs", @@ -27,94 +27,6 @@ ChromeUtils.defineLazyGetter(lazy, "contentPrefs", () => { ); }); -const BACKGROUND_WIDTH = 1024; -const BACKGROUND_HEIGHT = 768; - -/** - * A manager for hidden browsers. Responsible for creating and destroying a - * hidden frame to hold them. - * All of this is copied from PageDataService.sys.mjs - */ -class HiddenBrowserManager { - /** - * The hidden frame if one has been created. - * - * @type {HiddenFrame | null} - */ - #frame = null; - /** - * The number of hidden browser elements currently in use. - * - * @type {number} - */ - #browsers = 0; - - /** - * Creates and returns a new hidden browser. - * - * @returns {Browser} - */ - async #acquireBrowser() { - this.#browsers++; - if (!this.#frame) { - this.#frame = new lazy.HiddenFrame(); - } - - let frame = await this.#frame.get(); - let doc = frame.document; - let browser = doc.createXULElement("browser"); - browser.setAttribute("remote", "true"); - browser.setAttribute("type", "content"); - browser.setAttribute( - "style", - ` - width: ${BACKGROUND_WIDTH}px; - min-width: ${BACKGROUND_WIDTH}px; - height: ${BACKGROUND_HEIGHT}px; - min-height: ${BACKGROUND_HEIGHT}px; - ` - ); - browser.setAttribute("maychangeremoteness", "true"); - doc.documentElement.appendChild(browser); - - return browser; - } - - /** - * Releases the given hidden browser. - * - * @param {Browser} browser - * The hidden browser element. - */ - #releaseBrowser(browser) { - browser.remove(); - - this.#browsers--; - if (this.#browsers == 0) { - this.#frame.destroy(); - this.#frame = null; - } - } - - /** - * Calls a callback function with a new hidden browser. - * This function will return whatever the callback function returns. - * - * @param {Callback} callback - * The callback function will be called with the browser element and may - * be asynchronous. - * @returns {T} - */ - async withHiddenBrowser(callback) { - let browser = await this.#acquireBrowser(); - try { - return await callback(browser); - } finally { - this.#releaseBrowser(browser); - } - } -} - export class UserCharacteristicsPageService { classId = Components.ID("{ce3e9659-e311-49fb-b18b-7f27c6659b23}"); QueryInterface = ChromeUtils.generateQI([ @@ -124,13 +36,6 @@ export class UserCharacteristicsPageService { _initialized = false; _isParentProcess = false; - /** - * A manager for hidden browsers. - * - * @type {HiddenBrowserManager} - */ - _browserManager = new HiddenBrowserManager(); - /** * A map of hidden browsers to a resolve function that should be passed the * actor that was created for the browser. @@ -178,7 +83,7 @@ export class UserCharacteristicsPageService { remoteTypes: ["privilegedabout"], }); - return this._browserManager.withHiddenBrowser(async browser => { + return lazy.HiddenBrowserManager.withHiddenBrowser(async browser => { lazy.console.debug(`In withHiddenBrowser`); try { let { promise, resolve } = Promise.withResolvers(); diff --git a/toolkit/modules/HiddenFrame.sys.mjs b/toolkit/modules/HiddenFrame.sys.mjs index 42e0ef8f6e0a..3edbca131447 100644 --- a/toolkit/modules/HiddenFrame.sys.mjs +++ b/toolkit/modules/HiddenFrame.sys.mjs @@ -2,10 +2,21 @@ * 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/. */ +/** + * This module contains `HiddenFrame`, a class which creates a windowless browser, + * and `HiddenBrowserManager` which is a singleton that can be used to manage + * creating and using multiple hidden frames. + */ + const XUL_PAGE = Services.io.newURI("chrome://global/content/win.xhtml"); const gAllHiddenFrames = new Set(); +// The screen sizes to use for the background browser created by +// `HiddenBrowserManager`. +const BACKGROUND_WIDTH = 1024; +const BACKGROUND_HEIGHT = 768; + let cleanupRegistered = false; function ensureCleanupRegistered() { if (!cleanupRegistered) { @@ -121,3 +132,87 @@ export class HiddenFrame { this.#browser.loadURI(XUL_PAGE, loadURIOptions); } } + +/** + * A manager for hidden browsers. Responsible for creating and destroying a + * hidden frame to hold them. + */ +export const HiddenBrowserManager = new (class HiddenBrowserManager { + /** + * The hidden frame if one has been created. + * + * @type {HiddenFrame | null} + */ + #frame = null; + /** + * The number of hidden browser elements currently in use. + * + * @type {number} + */ + #browsers = 0; + + /** + * Creates and returns a new hidden browser. + * + * @returns {Browser} + */ + async #acquireBrowser() { + this.#browsers++; + if (!this.#frame) { + this.#frame = new HiddenFrame(); + } + + let frame = await this.#frame.get(); + let doc = frame.document; + let browser = doc.createXULElement("browser"); + browser.setAttribute("remote", "true"); + browser.setAttribute("type", "content"); + browser.setAttribute( + "style", + ` + width: ${BACKGROUND_WIDTH}px; + min-width: ${BACKGROUND_WIDTH}px; + height: ${BACKGROUND_HEIGHT}px; + min-height: ${BACKGROUND_HEIGHT}px; + ` + ); + browser.setAttribute("maychangeremoteness", "true"); + doc.documentElement.appendChild(browser); + + return browser; + } + + /** + * Releases the given hidden browser. + * + * @param {Browser} browser + * The hidden browser element. + */ + #releaseBrowser(browser) { + browser.remove(); + + this.#browsers--; + if (this.#browsers == 0) { + this.#frame.destroy(); + this.#frame = null; + } + } + + /** + * Calls a callback function with a new hidden browser. + * This function will return whatever the callback function returns. + * + * @param {Callback} callback + * The callback function will be called with the browser element and may + * be asynchronous. + * @returns {T} + */ + async withHiddenBrowser(callback) { + let browser = await this.#acquireBrowser(); + try { + return await callback(browser); + } finally { + this.#releaseBrowser(browser); + } + } +})();