tor-browser

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

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:
Mremote/webdriver-bidi/modules/root/browsingContext.sys.mjs | 19+++++++++++++++++--
Mremote/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs | 34+++++++++++++++++++++++++---------
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; } }