1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * This module contains `HiddenFrame`, a class which creates a windowless browser,
7 * and `HiddenBrowserManager` which is a singleton that can be used to manage
8 * creating and using multiple hidden frames.
11 const XUL_PAGE = Services.io.newURI("chrome://global/content/win.xhtml");
13 const gAllHiddenFrames = new Set();
15 // The screen sizes to use for the background browser created by
16 // `HiddenBrowserManager`.
17 const BACKGROUND_WIDTH = 1024;
18 const BACKGROUND_HEIGHT = 768;
20 let cleanupRegistered = false;
21 function ensureCleanupRegistered() {
22 if (!cleanupRegistered) {
23 cleanupRegistered = true;
24 Services.obs.addObserver(function () {
25 for (let hiddenFrame of gAllHiddenFrames) {
26 hiddenFrame.destroy();
33 * A hidden frame class. It takes care of creating a windowless browser and
34 * passing the window containing a blank XUL <window> back.
36 export class HiddenFrame {
44 * Gets the |contentWindow| of the hidden frame. Creates the frame if needed.
46 * @returns {Promise} Returns a promise which is resolved when the hidden frame has finished
50 if (!this.#deferred) {
51 this.#deferred = Promise.withResolvers();
55 return this.#deferred.promise;
59 * Fetch a sync ref to the window inside the frame (needed for the add-on SDK).
61 * @returns {DOMWindow}
65 return this.#browser.document.ownerGlobal;
69 * Destroys the browser, freeing resources.
74 this.#webProgress.removeProgressListener(this.#listener);
75 this.#listener = null;
76 this.#webProgress = null;
79 this.#deferred = null;
81 gAllHiddenFrames.delete(this);
82 this.#browser.close();
88 ensureCleanupRegistered();
89 let chromeFlags = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW;
90 if (Services.appinfo.fissionAutostart) {
91 chromeFlags |= Ci.nsIWebBrowserChrome.CHROME_FISSION_WINDOW;
93 this.#browser = Services.appShell.createWindowlessBrowser(
97 this.#browser.QueryInterface(Ci.nsIInterfaceRequestor);
98 gAllHiddenFrames.add(this);
99 this.#webProgress = this.#browser.getInterface(Ci.nsIWebProgress);
101 QueryInterface: ChromeUtils.generateQI([
102 "nsIWebProgressListener",
103 "nsIWebProgressListener2",
104 "nsISupportsWeakReference",
107 this.#listener.onStateChange = (wbp, request, stateFlags) => {
111 if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
112 this.#webProgress.removeProgressListener(this.#listener);
113 this.#listener = null;
114 this.#webProgress = null;
115 // Get the window reference via the document.
116 this.#frame = this.#browser.document.ownerGlobal;
117 this.#deferred.resolve(this.#frame);
120 this.#webProgress.addProgressListener(
122 Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
124 let docShell = this.#browser.docShell;
125 let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
126 docShell.createAboutBlankDocumentViewer(systemPrincipal, systemPrincipal);
127 let browsingContext = this.#browser.browsingContext;
128 browsingContext.useGlobalHistory = false;
129 let loadURIOptions = {
130 triggeringPrincipal: systemPrincipal,
132 this.#browser.loadURI(XUL_PAGE, loadURIOptions);
137 * A manager for hidden browsers. Responsible for creating and destroying a
138 * hidden frame to hold them.
140 export const HiddenBrowserManager = new (class HiddenBrowserManager {
142 * The hidden frame if one has been created.
144 * @type {HiddenFrame | null}
148 * The number of hidden browser elements currently in use.
155 * Creates and returns a new hidden browser.
159 async #acquireBrowser() {
162 this.#frame = new HiddenFrame();
165 let frame = await this.#frame.get();
166 let doc = frame.document;
167 let browser = doc.createXULElement("browser");
168 browser.setAttribute("remote", "true");
169 browser.setAttribute("type", "content");
170 browser.setAttribute(
173 width: ${BACKGROUND_WIDTH}px;
174 min-width: ${BACKGROUND_WIDTH}px;
175 height: ${BACKGROUND_HEIGHT}px;
176 min-height: ${BACKGROUND_HEIGHT}px;
179 browser.setAttribute("maychangeremoteness", "true");
180 doc.documentElement.appendChild(browser);
186 * Releases the given hidden browser.
188 * @param {Browser} browser
189 * The hidden browser element.
191 #releaseBrowser(browser) {
195 if (this.#browsers == 0) {
196 this.#frame.destroy();
202 * Calls a callback function with a new hidden browser.
203 * This function will return whatever the callback function returns.
205 * @param {Callback} callback
206 * The callback function will be called with the browser element and may
210 async withHiddenBrowser(callback) {
211 let browser = await this.#acquireBrowser();
213 return await callback(browser);
215 this.#releaseBrowser(browser);