commit b20d5047f4186c827d9617043661895454991039
parent 7b810d534e1d349aa05bef50d8578019082627de
Author: Henrik Skupin <mail@hskupin.info>
Date: Wed, 24 Dec 2025 10:02:09 +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:
4 files changed, 141 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,32 @@ class WindowManager {
}
/**
- * Wait until the initial application window has been opened and loaded.
+ * Wait until the browser window is initialized and loaded.
+ *
+ * @param {ChromeWindow} window
+ * The chrome window to check for completed loading.
*
- * @returns {Promise<WindowProxy>}
- * A promise that resolved to the application window.
+ * @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) {
+ 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",
- }
- );
+ if (
+ window.document.documentURI === AppConstants.BROWSER_CHROME_URL &&
+ !(window.gBrowserInit && window.gBrowserInit.delayedStartupFinished)
+ ) {
+ // 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],
)