tor-browser

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

commit a689946d0f7a6ca9b5b7aee00826cac863a6c63b
parent 75486f100a5da0731574accd4690e36410879eac
Author: Alexandra Borovova <aborovova@mozilla.com>
Date:   Thu,  4 Dec 2025 12:27:58 +0000

Bug 2000651 - Add an API to BrowsingContext to override screen dimensions. r=firefox-style-system-reviewers,emilio

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

Diffstat:
Mdocshell/base/BrowsingContext.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Mdom/base/nsScreen.cpp | 17+++++++++++++++--
Mdom/base/test/browser.toml | 2++
Adom/base/test/browser_screen_area_override.js | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/chrome-webidl/BrowsingContext.webidl | 7+++++++
Mlayout/style/nsMediaFeatures.cpp | 9+++++++++
6 files changed, 268 insertions(+), 2 deletions(-)

diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h @@ -207,6 +207,9 @@ struct EmbedderColorSchemes { * context. */ \ FIELD(GVAudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \ + FIELD(ScreenHeightOverride, uint64_t) \ + FIELD(ScreenWidthOverride, uint64_t) \ + FIELD(HasScreenAreaOverride, bool) \ /* ScreenOrientation-related APIs */ \ FIELD(CurrentOrientationAngle, float) \ FIELD(CurrentOrientationType, mozilla::dom::OrientationType) \ @@ -712,6 +715,49 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { return false; } + [[nodiscard]] nsresult SetScreenAreaOverride(uint64_t aScreenWidth, + uint64_t aScreenHeight) { + if (GetHasScreenAreaOverride() && + GetScreenWidthOverride() == aScreenWidth && + GetScreenHeightOverride() == aScreenHeight) { + return NS_OK; + } + + Transaction txn; + txn.SetScreenWidthOverride(aScreenWidth); + txn.SetScreenHeightOverride(aScreenHeight); + txn.SetHasScreenAreaOverride(true); + return txn.Commit(this); + } + + void SetScreenAreaOverride(uint64_t aScreenWidth, uint64_t aScreenHeight, + ErrorResult& aRv) { + MOZ_ASSERT(IsTop()); + + if (NS_FAILED(SetScreenAreaOverride(aScreenWidth, aScreenHeight))) { + aRv.ThrowInvalidStateError("Browsing context is discarded"); + } + } + + void ResetScreenAreaOverride() { + MOZ_ASSERT(IsTop()); + + (void)SetHasScreenAreaOverride(false); + } + + bool HasScreenAreaOverride() const { + return Top()->GetHasScreenAreaOverride(); + } + + Maybe<CSSIntSize> GetScreenAreaOverride() { + if (!HasScreenAreaOverride()) { + return Nothing(); + } + CSSIntSize screenSize(Top()->GetScreenWidthOverride(), + Top()->GetScreenHeightOverride()); + return Some(screenSize); + } + // ScreenOrientation related APIs [[nodiscard]] nsresult SetCurrentOrientation(OrientationType aType, float aAngle) { diff --git a/dom/base/nsScreen.cpp b/dom/base/nsScreen.cpp @@ -7,6 +7,7 @@ #include "nsScreen.h" #include "mozilla/GeckoBindings.h" +#include "mozilla/dom/BrowsingContextBinding.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/widget/ScreenManager.h" @@ -69,7 +70,7 @@ CSSIntRect nsScreen::GetRect() { } // Here we manipulate the value of aRect to represent the screen size, - // if in RDM. + // if there is an override set with WebDriver BiDi or in RDM. if (nsPIDOMWindowInner* owner = GetOwnerWindow()) { if (Document* doc = owner->GetExtantDoc()) { Maybe<CSSIntSize> deviceSize = @@ -79,6 +80,12 @@ CSSIntRect nsScreen::GetRect() { return {0, 0, size.width, size.height}; } } + + if (BrowsingContext* bc = owner->GetBrowsingContext()) { + if (auto size = bc->GetScreenAreaOverride()) { + return {{}, *size}; + } + } } nsDeviceContext* context = GetDeviceContext(); @@ -104,7 +111,7 @@ CSSIntRect nsScreen::GetAvailRect() { } // Here we manipulate the value of aRect to represent the screen size, - // if in RDM. + // if there is an override set with WebDriver BiDi or in RDM. if (nsPIDOMWindowInner* owner = GetOwnerWindow()) { if (Document* doc = owner->GetExtantDoc()) { Maybe<CSSIntSize> deviceSize = @@ -114,6 +121,12 @@ CSSIntRect nsScreen::GetAvailRect() { return {0, 0, size.width, size.height}; } } + + if (BrowsingContext* bc = owner->GetBrowsingContext()) { + if (auto size = bc->GetScreenAreaOverride()) { + return {{}, *size}; + } + } } nsDeviceContext* context = GetDeviceContext(); diff --git a/dom/base/test/browser.toml b/dom/base/test/browser.toml @@ -155,6 +155,8 @@ support-files = [ "file_browser_refresh_iframe.sjs", ] +["browser_screen_area_override.js"] + ["browser_screen_orientation_override.js"] ["browser_script_loader_js_cache_basic.js"] diff --git a/dom/base/test/browser_screen_area_override.js b/dom/base/test/browser_screen_area_override.js @@ -0,0 +1,189 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PAGE_URL = + "https://example.com/document-builder.sjs?html=<h1>Test screen dimensions</h1>"; + +add_task(async function test_set_screen_area_override() { + const tab = BrowserTestUtils.addTab(gBrowser, PAGE_URL); + const browser = gBrowser.getBrowserForTab(tab); + + await BrowserTestUtils.browserLoaded(browser); + + info("Get default screen dimensions"); + const defaultScreenWidth = await getScreenWidth(browser); + const defaultScreenAvailWidth = await getScreenAvailWidth(browser); + const defaultScreenHeight = await getScreenHeight(browser); + const defaultScreenAvailHeight = await getScreenAvailHeight(browser); + + info("Set screen dimensions"); + const screenWidthOverride1 = 200; + const screenHeightOverride1 = 100; + browser.browsingContext.setScreenAreaOverride( + screenWidthOverride1, + screenHeightOverride1 + ); + + await assertOrientationOverride( + browser, + screenWidthOverride1, + screenHeightOverride1, + screenWidthOverride1, + screenHeightOverride1 + ); + + info("Set screen dimensions override"); + const screenWidthOverride2 = 250; + const screenHeightOverride2 = 200; + browser.browsingContext.setScreenAreaOverride( + screenWidthOverride2, + screenHeightOverride2 + ); + + await assertOrientationOverride( + browser, + screenWidthOverride2, + screenHeightOverride2, + screenWidthOverride2, + screenHeightOverride2 + ); + + info("Reset screen dimensions override"); + browser.browsingContext.resetScreenAreaOverride(); + + await assertOrientationOverride( + browser, + defaultScreenWidth, + defaultScreenHeight, + defaultScreenAvailWidth, + defaultScreenAvailHeight + ); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_set_screen_area_override_in_different_contexts() { + const tab1 = BrowserTestUtils.addTab(gBrowser, PAGE_URL); + const browser1 = gBrowser.getBrowserForTab(tab1); + + await BrowserTestUtils.browserLoaded(browser1); + + const tab2 = BrowserTestUtils.addTab(gBrowser, PAGE_URL); + const browser2 = gBrowser.getBrowserForTab(tab2); + + await BrowserTestUtils.browserLoaded(browser2); + + info("Get default screen dimensions"); + const defaultScreenWidth = await getScreenWidth(browser1); + const defaultScreenAvailWidth = await getScreenAvailWidth(browser1); + const defaultScreenHeight = await getScreenHeight(browser1); + const defaultScreenAvailHeight = await getScreenAvailHeight(browser1); + + info("Set screen dimensions to the first tab"); + const screenWidthOverride1 = 200; + const screenHeightOverride1 = 100; + browser1.browsingContext.setScreenAreaOverride( + screenWidthOverride1, + screenHeightOverride1 + ); + + await assertOrientationOverride( + browser1, + screenWidthOverride1, + screenHeightOverride1, + screenWidthOverride1, + screenHeightOverride1 + ); + + info("Make sure that in the second tab screen dimensions are not overridden"); + await assertOrientationOverride( + browser2, + defaultScreenWidth, + defaultScreenHeight, + defaultScreenAvailWidth, + defaultScreenAvailHeight + ); + + info("Reset screen dimensions override"); + browser1.browsingContext.resetScreenAreaOverride(); + + await assertOrientationOverride( + browser1, + defaultScreenWidth, + defaultScreenHeight, + defaultScreenAvailWidth, + defaultScreenAvailHeight + ); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +async function assertOrientationOverride( + browser, + expectedWidthValue, + expectedHeightValue, + expectedAvailWidthValue, + expectedAvailHeightValue +) { + is( + await getScreenWidth(browser), + expectedWidthValue, + "screen.width has expected value" + ); + is( + await getScreenAvailWidth(browser), + expectedAvailWidthValue, + "screen.availWidth has expected value" + ); + is( + await getScreenHeight(browser), + expectedHeightValue, + "screen.height has expected value" + ); + is( + await getScreenAvailHeight(browser), + expectedAvailHeightValue, + "screen.availHeight has expected value" + ); + + const valueBiggerThanWidth = expectedWidthValue + 1; + is( + await matchMediaQuery(browser, valueBiggerThanWidth), + false, + `matches "(min-device-width: ${valueBiggerThanWidth}px)" queries correctly` + ); + + const valueSmallerThanWidth = expectedWidthValue - 1; + is( + await matchMediaQuery(browser, valueSmallerThanWidth), + true, + `matches "(min-device-width: ${valueSmallerThanWidth}px)" queries correctly` + ); +} + +async function getScreenHeight(browser) { + return SpecialPowers.spawn(browser, [], () => content.screen.height); +} + +async function getScreenAvailHeight(browser) { + return SpecialPowers.spawn(browser, [], () => content.screen.availHeight); +} + +async function getScreenWidth(browser) { + return SpecialPowers.spawn(browser, [], () => content.screen.width); +} + +async function getScreenAvailWidth(browser) { + return SpecialPowers.spawn(browser, [], () => content.screen.availWidth); +} + +async function matchMediaQuery(browser, value) { + return SpecialPowers.spawn( + browser, + [value], + value => content.matchMedia(`(min-device-width: ${value}px)`).matches + ); +} diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl @@ -198,6 +198,13 @@ interface BrowsingContext { [SetterThrows] attribute boolean useGlobalHistory; // Extension to give chrome JS the ability to set the window screen + // dimensions override with WebDriver BiDi. + [Throws] undefined setScreenAreaOverride(unsigned long long screenWidth, unsigned long long screenHeight); + // Extension to give chrome JS the ability to reset the window screen + // dimensions override with WebDriver BiDi. + undefined resetScreenAreaOverride(); + + // Extension to give chrome JS the ability to set the window screen // orientation override with WebDriver BiDi and DevTools. [Throws] undefined setOrientationOverride(OrientationType type, float rotationAngle); // Extension to give chrome JS the ability to reset the window screen diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp @@ -72,6 +72,15 @@ static nsSize GetDeviceSize(const Document& aDocument) { return CSSPixel::ToAppUnits(deviceSize.value()); } + // Media queries in documents should use an override set with WebDriver BiDi + // if it exists. + if (dom::BrowsingContext* bc = aDocument.GetBrowsingContext()) { + Maybe<CSSIntSize> screenSize = bc->GetScreenAreaOverride(); + if (screenSize.isSome()) { + return CSSPixel::ToAppUnits(screenSize.value()); + } + } + nsPresContext* pc = aDocument.GetPresContext(); // NOTE(emilio): We should probably figure out how to return an appropriate // device size here, though in a multi-screen world that makes no sense