tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7a41387427e9bac62b8bf0895a8cf05ec88d822f
parent 9c534e8a5910d797fbd7f1b931450f0d9875ead3
Author: Henrik Skupin <mail@hskupin.info>
Date:   Mon, 29 Dec 2025 10:48:23 +0000

Bug 1891028 - [remote] Wait for "browser-delayed-startup-finished" when opening a new browser window. r=Sasha

Differential Revision: https://phabricator.services.mozilla.com/D277429

Diffstat:
Mremote/marionette/driver.sys.mjs | 5+++--
Mremote/shared/WindowManager.sys.mjs | 75+++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mremote/shared/test/browser/browser_WindowManager.js | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtesting/marionette/harness/marionette_harness/runner/mixins/window_manager.py | 29++++++++++++-----------------
4 files changed, 153 insertions(+), 49 deletions(-)

diff --git a/remote/marionette/driver.sys.mjs b/remote/marionette/driver.sys.mjs @@ -678,8 +678,9 @@ GeckoDriver.prototype.newSession = async function (cmd) { lazy.logger.debug(`Waiting for initial application window`); await lazy.Marionette.browserStartupFinished; - const appWin = - await lazy.windowManager.waitForInitialApplicationWindowLoaded(); + // This call includes a fallback to "mail:3pane" as well. + const appWin = Services.wm.getMostRecentBrowserWindow(); + await lazy.windowManager.waitForChromeWindowLoaded(appWin); if (lazy.MarionettePrefs.clickToStart) { Services.prompt.alert( diff --git a/remote/shared/WindowManager.sys.mjs b/remote/shared/WindowManager.sys.mjs @@ -2,6 +2,8 @@ * 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 { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { @@ -337,8 +339,12 @@ class WindowManager { * @param {string=} options.userContextId * The id of the user context which should own the initial tab of the new * window. - * @returns {Promise} + * + * @returns {Promise<ChromeWindow>} * A promise resolving to the newly created chrome window. + * + * @throws {UnsupportedOperationError} + * When opening a new browser window is not supported. */ async openBrowserWindow(options = {}) { let { @@ -391,7 +397,10 @@ class WindowManager { await this.focusWindow(openerWindow); } - return browser.ownerGlobal; + const chromeWindow = browser.ownerGlobal; + await this.waitForChromeWindowLoaded(chromeWindow); + + return chromeWindow; } default: @@ -470,38 +479,44 @@ class WindowManager { } /** - * Wait until the initial application window has been opened and loaded. + * Wait until the browser window is initialized and loaded. * - * @returns {Promise<WindowProxy>} - * A promise that resolved to the application window. + * @param {ChromeWindow} window + * The chrome window to check for completed loading. + * + * @returns {Promise} + * A promise that resolves when the chrome window finished loading. */ - waitForInitialApplicationWindowLoaded() { - return new lazy.TimedPromise( - async resolve => { - // This call includes a fallback to "mail:3pane" as well. - const win = Services.wm.getMostRecentBrowserWindow(); - - const windowLoaded = lazy.waitForObserverTopic( - "browser-delayed-startup-finished", - { - checkFn: subject => (win !== null ? subject == win : true), - } - ); + async waitForChromeWindowLoaded(window) { + const loaded = + window.document.readyState === "complete" && + !window.document.isUncommittedInitialDocument; - // The current window has already been finished loading. - if (win && win.document.readyState == "complete") { - resolve(win); - return; - } + if (!loaded) { + lazy.logger.trace( + `Chrome window not loaded yet. Waiting for "load" event` + ); + await new lazy.EventPromise(window, "load"); + } - // Wait for the next browser/mail window to open and finished loading. - const { subject } = await windowLoaded; - resolve(subject); - }, - { - errorMessage: "No applicable application window found", - } - ); + // Only Firefox stores the delayed startup finished status, allowing + // it to be checked at any time. On Android, this is unnecessary + // because there is only a single window, and we already wait for + // that window during startup. + if ( + lazy.AppInfo.isFirefox && + window.document.documentURI === AppConstants.BROWSER_CHROME_URL && + !(window.gBrowserInit && window.gBrowserInit.delayedStartupFinished) + ) { + lazy.logger.trace( + `Browser window not initialized yet. Waiting for startup finished` + ); + + // If it's a browser window wait for it to be fully initialized. + await lazy.waitForObserverTopic("browser-delayed-startup-finished", { + checkFn: subject => subject === window, + }); + } } #setChromeWindowForBrowsingContext(context) { diff --git a/remote/shared/test/browser/browser_WindowManager.js b/remote/shared/test/browser/browser_WindowManager.js @@ -328,3 +328,96 @@ add_task(async function test_getWindowById() { windowManager.destroy(); } }); + +add_task(async function test_waitForChromeWindowLoaded_newBrowserWindow() { + const win = Services.ww.openWindow( + null, + AppConstants.BROWSER_CHROME_URL, + "_blank", + "chrome,all,dialog=no", + null + ); + + try { + ok( + !win.gBrowserInit?.delayedStartupFinished, + "Browser window not finished delayed startup" + ); + + await windowManager.waitForChromeWindowLoaded(win); + + ok( + win.gBrowserInit.delayedStartupFinished, + "Browser window finished delayed startup" + ); + is( + win.document.readyState, + "complete", + "Window document is in complete state" + ); + ok( + !win.document.isUncommittedInitialDocument, + "Window document is not an uncommitted initial document" + ); + } finally { + await BrowserTestUtils.closeWindow(win); + } +}); + +add_task(async function test_waitForChromeWindowLoaded_alreadyLoadedWindow() { + const win = await BrowserTestUtils.openNewBrowserWindow(); + + try { + ok( + win.gBrowserInit.delayedStartupFinished, + "Browser window is already fully loaded" + ); + + await windowManager.waitForChromeWindowLoaded(win); + + is( + win.document.readyState, + "complete", + "Window document is in complete state" + ); + ok( + !win.document.isUncommittedInitialDocument, + "Window document is not an uncommitted initial document" + ); + } finally { + await BrowserTestUtils.closeWindow(win); + } +}); + +add_task( + async function test_waitForChromeWindowLoaded_nonBrowserChromeWindow() { + const win = Services.ww.openWindow( + gBrowser.ownerGlobal, + "chrome://browser/content/pageinfo/pageInfo.xhtml", + "_blank", + "chrome,dialog=no,all", + null + ); + + try { + await windowManager.waitForChromeWindowLoaded(win); + + isnot( + win.document.documentURI, + AppConstants.BROWSER_CHROME_URL, + "Window is not a browser window" + ); + is( + win.document.readyState, + "complete", + "Window document is in complete state" + ); + ok( + !win.document.isUncommittedInitialDocument, + "Window document is not an uncommitted initial document" + ); + } finally { + await BrowserTestUtils.closeWindow(win); + } + } +); diff --git a/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py b/testing/marionette/harness/marionette_harness/runner/mixins/window_manager.py @@ -96,27 +96,22 @@ class WindowManagerMixin(object): const { NavigableManager } = ChromeUtils.importESModule( "chrome://remote/content/shared/NavigableManager.sys.mjs" ); + const { windowManager } = ChromeUtils.importESModule( + "chrome://remote/content/shared/WindowManager.sys.mjs" + ); - const isLoaded = window => - window?.document.readyState === "complete" && - !window?.document.isUncommittedInitialDocument; + const browsingContext = + NavigableManager.getBrowsingContextById(handle); + const window = + windowManager.getChromeWindowForBrowsingContext(browsingContext); - const browsingContext = NavigableManager.getBrowsingContextById(handle); - const targetWindow = browsingContext?.window; + (async function() { + if (window) { + await windowManager.waitForChromeWindowLoaded(window); + } - if (isLoaded(targetWindow)) { resolve(); - } else { - const onLoad = () => { - if (isLoaded(targetWindow)) { - targetWindow.removeEventListener("load", onLoad); - resolve(); - } else { - dump(`** Target window not loaded yet. Waiting for the next "load" event\n`); - } - }; - targetWindow.addEventListener("load", onLoad); - } + })(); """, script_args=[handle], )