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:
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