commit dba09acbc4fd06e4056655630e45f2c08570f078
parent d1180226a76e68cf5ad853792faad5f70364b6f3
Author: Julian Descottes <jdescottes@mozilla.com>
Date: Thu, 11 Dec 2025 07:24:33 +0000
Bug 2003857 - [bidi] Wait for document to be visible in browsingContext.create with background=false r=Sasha,whimboo
Differential Revision: https://phabricator.services.mozilla.com/D275733
Diffstat:
2 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/root/browsingContext.sys.mjs
@@ -135,6 +135,7 @@ export const OriginType = {
};
const TIMEOUT_SET_HISTORY_INDEX = 1000;
+const TIMEOUT_WAIT_FOR_VISIBILITY = 250;
/**
* Enum of user prompt types supported by the browsingContext.handleUserPrompt
@@ -782,6 +783,19 @@ class BrowsingContextModule extends RootBiDiModule {
// Force a reflow by accessing `clientHeight` (see Bug 1847044).
browser.parentElement.clientHeight;
+ if (!background && !lazy.AppInfo.isAndroid) {
+ // See Bug 2002097, on slow platforms, the newly created tab might not be
+ // visible immediately.
+ await this.#waitForVisibilityState(
+ browser.browsingContext,
+ "visible",
+ // Waiting for visibility can potentially be racy. If several contexts
+ // are created in parallel, we might not be able to catch the document
+ // in the expected state.
+ { timeout: TIMEOUT_WAIT_FOR_VISIBILITY * lazy.getTimeoutMultiplier() }
+ );
+ }
+
return {
context: lazy.NavigableManager.getIdForBrowser(browser),
};
@@ -2420,11 +2434,12 @@ class BrowsingContextModule extends RootBiDiModule {
}
}
- #waitForVisibilityState(browsingContext, expectedState) {
+ #waitForVisibilityState(browsingContext, expectedState, options = {}) {
+ const { timeout } = options;
return this._forwardToWindowGlobal(
"_awaitVisibilityState",
browsingContext.id,
- { value: expectedState },
+ { value: expectedState, timeout },
{ retryOnAbort: true }
);
}
diff --git a/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs b/remote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs
@@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
ClipRectangleType:
"chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
+ EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
LoadListener: "chrome://remote/content/shared/listeners/LoadListener.sys.mjs",
LocatorType:
"chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
@@ -488,22 +489,37 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
* Waits until the visibility state of the document has the expected value.
*
* @param {object} options
+ * @param {number=} options.timeout
+ * Timeout in ms. Optional, if not provided, the command will only resolve
+ * when the expected state is met.
* @param {number} options.value
* Expected value of the visibility state.
*
* @returns {Promise}
- * Promise that resolves when the visibility state has the expected value.
+ * Promise that resolves when the visibility state has the expected value,
+ * or the timeout has been reached.
*/
async _awaitVisibilityState(options) {
- const { value } = options;
+ const { timeout = null, value } = options;
const win = this.messageHandler.window;
- if (win.document.visibilityState !== value) {
- await new Promise(r => {
- // The visibilityState can only be hidden or visible, so if the current
- // value is not the expected one, the next visibilitychange event is
- // guaranteed to have the correct value.
- win.document.addEventListener("visibilitychange", r, { once: true });
- });
+
+ if (win.document.visibilityState === value) {
+ // If the document visibilityState already has the expected value, resolve
+ // immediately.
+ return;
+ }
+
+ try {
+ // Otherwise, wait for the next visibilitychange event.
+ await new lazy.EventPromise(win, "visibilitychange", { timeout });
+ } catch (e) {
+ if (e instanceof lazy.error.TimeoutError) {
+ // Swallow the exception thrown by the EventPromise if we simply
+ // reached the timeout. Here the timeout is meant as an escape hatch,
+ // but we should still resolve
+ return;
+ }
+ throw e;
}
}