Firefox · Core
CVE-2026-6762
Logic Error in Core
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifbrowser/actors/DOMFullscreenParent.sys.mjs |
modified | |
showFullScreenbrowser/base/content/browser-fullScreenAndPointerLock.js |
modified | |
ifbrowser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js |
modified | |
add_setupbrowser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js |
modified | |
add_taskbrowser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js |
modified |
Files Changed
browser/actors/DOMFullscreenParent.sys.mjsbrowser/base/content/browser-fullScreenAndPointerLock.jsbrowser/base/content/test/fullscreen/browser.tomlbrowser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
Patch
diff --git a/browser/actors/DOMFullscreenParent.sys.mjs b/browser/actors/DOMFullscreenParent.sys.mjs
index 153bc33b75e..a15e55a7c1f 100644
--- a/browser/actors/DOMFullscreenParent.sys.mjs
+++ b/browser/actors/DOMFullscreenParent.sys.mjs
@@ -142,9 +142,7 @@ export class DOMFullscreenParent extends JSWindowActorParent {
case "DOMFullscreen:NewOrigin": {
// Don't show the warning if we've already exited fullscreen.
if (window.document.fullscreen) {
- window.PointerlockFsWarning.showFullScreen(
- aMessage.data.originNoSuffix
- );
+ window.PointerlockFsWarning.showFullScreen(topBrowsingContext);
}
this.updateFullscreenWindowReference(window);
break;
@@ -222,7 +220,7 @@ export class DOMFullscreenParent extends JSWindowActorParent {
if (!this.hasBeenDestroyed() && this.requestOrigin) {
window.PointerlockFsWarning.showFullScreen(
- this.requestOrigin.manager.documentPrincipal.originNoSuffix
+ this.requestOrigin.browsingContext
);
}
break;
diff --git a/browser/base/content/browser-fullScreenAndPointerLock.js b/browser/base/content/browser-fullScreenAndPointerLock.js
index 504bafe7f51..cd46701e9f4 100644
--- a/browser/base/content/browser-fullScreenAndPointerLock.js
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -46,10 +46,15 @@ var PointerlockFsWarning = {
}
},
- showFullScreen(aOrigin) {
+ // Show info that top level has entered fullscreen. Ultimately, it is always
+ // ancestors who are in control of what is displayed on screen.
+ // By always displaying the top level, we try to make that clear to the user.
+ showFullScreen(browsingContext) {
+ const origin =
+ browsingContext.top.currentWindowGlobal.documentPrincipal.originNoSuffix;
let timeout = Services.prefs.getIntPref("full-screen-api.warning.timeout");
let delay = Services.prefs.getIntPref("full-screen-api.warning.delay");
- this.show(aOrigin, "fullscreen-warning", timeout, delay);
+ this.show(origin, "fullscreen-warning", timeout, delay);
},
// Shows a warning that the site has entered fullscreen or
diff --git a/browser/base/content/test/fullscreen/browser.toml b/browser/base/content/test/fullscreen/browser.toml
index 1c6f81b12af..bd4278bfaa1 100644
--- a/browser/base/content/test/fullscreen/browser.toml
+++ b/browser/base/content/test/fullscreen/browser.toml
@@ -71,6 +71,9 @@ skip-if = [
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && opt", # Bug 1949995
]
+["browser_fullscreen_toplevel_warning.js"]
+support-files = ["fullscreen.html"]
+
["browser_fullscreen_ui_state.js"]
skip-if = [
"os == 'linux' && os_version == '22.04' && arch == 'x86_64' && display == 'wayland' && opt", # Bug 1972541
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js b/browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
new file mode 100644
index 00000000000..dcf2b8d9545
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
@@ -0,0 +1,191 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FULLSCREEN_PATH =
+ "/browser/browser/base/content/test/fullscreen/fullscreen.html";
+
+function getWarningDomain(warning) {
+ let textElem = warning.querySelector(".pointerlockfswarning-domain-text");
+ if (textElem.hidden) {
+ return null;
+ }
+ let args = textElem.getAttribute("data-l10n-args");
+ return args ? JSON.parse(args).domain : null;
+}
+
+async function waitForWarningState(aWarningElement, aExpectedState) {
+ await BrowserTestUtils.waitForAttribute(aExpectedState, aWarningElement, "");
+}
+
+add_setup(async function init() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["test.wait300msAfterTabSwitch", true],
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ],
+ });
+});
+
+// Bug 2021080 - Verify the fullscreen warning always displays the top-level domain,
+// not the origin of the cross-origin frame that requested fullscreen.
+add_task(async function test_fullscreen_warning_cross_origin_shows_toplevel() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ let warning = document.getElementById("fullscreen-warning");
+
+ await SpecialPowers.spawn(browser, [FULLSCREEN_PATH], async path => {
+ let iframe = content.document.createElement("iframe");
+ iframe.allow = "fullscreen";
+ iframe.src = `https://example.org${path}`;
+ let loaded = new Promise(r =>
+ iframe.addEventListener("load", r, { once: true })
+ );
+ content.document.body.appendChild(iframe);
+ await loaded;
+ });
+
+ let warningShown = waitForWarningState(warning, "onscreen");
+ await SpecialPowers.spawn(browser, [], async () => {
+ let frame = content.document.querySelector("iframe");
+ frame.focus();
+ await SpecialPowers.spawn(frame, [], () => {
+ content.document.getElementById("request").click();
+ });
+ });
+ await warningShown;
+
+ const innerDocIsFullscreened = await SpecialPowers.spawn(
+ browser,
+ [],
+ async () => {
+ let frame = content.document.querySelector("iframe");
+ return SpecialPowers.spawn(
+ frame,
+ [],
+ () => content.document.fullscreenElement != null
+ );
+ }
+ );
+ Assert.ok(
+ innerDocIsFullscreened,
+ "Cross-origin frame (example.org) is the active fullscreen document"
+ );
+ is(
+ getWarningDomain(warning),
+ "example.com",
+ "Warning shows top-level domain, not the active fullscreen frame's domain"
+ );
+
+ let warningHidden = waitForWarningState(warning, "hidden");
+ let exitPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "fullscreenchange",
+ false,
+ () => !document.fullscreenElement
+ );
+ document.getElementById("fullscreen-exit-button").click();
+ await Promise.all([exitPromise, warningHidden]);
+ });
+});
+
+// Bug 2021080 - Verify the fullscreen warning shows the top-level domain when each
+// of three nested cross-origin frames (top, middle, inner) requests fullscreen.
+add_task(async function test_fullscreen_warning_three_nested_origins() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ let warning = document.getElementById("fullscreen-warning");
+
+ // Build a 3-level nested structure:
+ // example.com (div > iframe[example.org (div > iframe[example.net])])
+ await SpecialPowers.spawn(browser, [FULLSCREEN_PATH], async path => {
+ let topDiv = content.document.createElement("div");
+ content.document.body.appendChild(topDiv);
+
+ let middleFrame = content.document.createElement("iframe");
+ middleFrame.allow = "fullscreen";
+ middleFrame.src = `https://example.org${path}`;
+ let loaded = new Promise(r =>
+ middleFrame.addEventListener("load", r, { once: true })
+ );
+ topDiv.appendChild(middleFrame);
+ await loaded;
+
+ await SpecialPowers.spawn(middleFrame, [path], async innerPath => {
+ let middleDiv = content.document.createElement("div");
+ content.document.body.appendChild(middleDiv);
+
+ let innerFrame = content.document.createElement("iframe");
+ innerFrame.allow = "fullscreen";
+ innerFrame.src = `https://example.net${innerPath}`;
+ let innerLoaded = new Promise(r =>
+ innerFrame.addEventListener("load", r, { once: true })
+ );
+ middleDiv.appendChild(innerFrame);
+ await innerLoaded;
+ });
+ });
+
+ async function exitFullscreen() {
+ let warningHidden = waitForWarningState(warning, "hidden");
+ let exitPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "fullscreenchange",
+ false,
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/browser/base/content/test/fullscreen/browser.toml b/browser/base/content/test/fullscreen/browser.toml
index 1c6f81b12af..bd4278bfaa1 100644
--- a/browser/base/content/test/fullscreen/browser.toml
+++ b/browser/base/content/test/fullscreen/browser.toml
@@ -71,6 +71,9 @@ skip-if = [
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && opt", # Bug 1949995
]
+["browser_fullscreen_toplevel_warning.js"]
+support-files = ["fullscreen.html"]
+
["browser_fullscreen_ui_state.js"]
skip-if = [
"os == 'linux' && os_version == '22.04' && arch == 'x86_64' && display == 'wayland' && opt", # Bug 1972541
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js b/browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
new file mode 100644
index 00000000000..dcf2b8d9545
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
@@ -0,0 +1,191 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FULLSCREEN_PATH =
+ "/browser/browser/base/content/test/fullscreen/fullscreen.html";
+
+function getWarningDomain(warning) {
+ let textElem = warning.querySelector(".pointerlockfswarning-domain-text");
+ if (textElem.hidden) {
+ return null;
+ }
+ let args = textElem.getAttribute("data-l10n-args");
+ return args ? JSON.parse(args).domain : null;
+}
+
+async function waitForWarningState(aWarningElement, aExpectedState) {
+ await BrowserTestUtils.waitForAttribute(aExpectedState, aWarningElement, "");
+}
+
+add_setup(async function init() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["test.wait300msAfterTabSwitch", true],
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ],
+ });
+});
+
+// Bug 2021080 - Verify the fullscreen warning always displays the top-level domain,
+// not the origin of the cross-origin frame that requested fullscreen.
+add_task(async function test_fullscreen_warning_cross_origin_shows_toplevel() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ let warning = document.getElementById("fullscreen-warning");
+
+ await SpecialPowers.spawn(browser, [FULLSCREEN_PATH], async path => {
+ let iframe = content.document.createElement("iframe");
+ iframe.allow = "fullscreen";
+ iframe.src = `https://example.org${path}`;
+ let loaded = new Promise(r =>
+ iframe.addEventListener("load", r, { once: true })
+ );
+ content.document.body.appendChild(iframe);
+ await loaded;
+ });
+
+ let warningShown = waitForWarningState(warning, "onscreen");
+ await SpecialPowers.spawn(browser, [], async () => {
+ let frame = content.document.querySelector("iframe");
+ frame.focus();
+ await SpecialPowers.spawn(frame, [], () => {
+ content.document.getElementById("request").click();
+ });
+ });
+ await warningShown;
+
+ const innerDocIsFullscreened = await SpecialPowers.spawn(
+ browser,
+ [],
+ async () => {
+ let frame = content.document.querySelector("iframe");
+ return SpecialPowers.spawn(
+ frame,
+ [],
+ () => content.document.fullscreenElement != null
+ );
+ }
+ );
+ Assert.ok(
+ innerDocIsFullscreened,
+ "Cross-origin frame (example.org) is the active fullscreen document"
+ );
+ is(
+ getWarningDomain(warning),
+ "example.com",
+ "Warning shows top-level domain, not the active fullscreen frame's domain"
+ );
+
+ let warningHidden = waitForWarningState(warning, "hidden");
+ let exitPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "fullscreenchange",
+ false,
+ () => !document.fullscreenElement
+ );
+ document.getElementById("fullscreen-exit-button").click();
+ await Promise.all([exitPromise, warningHidden]);
+ });
+});
+
+// Bug 2021080 - Verify the fullscreen warning shows the top-level domain when each
+// of three nested cross-origin frames (top, middle, inner) requests fullscreen.
+add_task(async function test_fullscreen_warning_three_nested_origins() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ let warning = document.getElementById("fullscreen-warning");
+
+ // Build a 3-level nested structure:
+ // example.com (div > iframe[example.org (div > iframe[example.net])])
+ await SpecialPowers.spawn(browser, [FULLSCREEN_PATH], async path => {
+ let topDiv = content.document.createElement("div");
+ content.document.body.appendChild(topDiv);
+
+ let middleFrame = content.document.createElement("iframe");
+ middleFrame.allow = "fullscreen";
+ middleFrame.src = `https://example.org${path}`;
+ let loaded = new Promise(r =>
+ middleFrame.addEventListener("load", r, { once: true })
+ );
+ topDiv.appendChild(middleFrame);
+ await loaded;
+
+ await SpecialPowers.spawn(middleFrame, [path], async innerPath => {
+ let middleDiv = content.document.createElement("div");
+ content.document.body.appendChild(middleDiv);
+
+ let innerFrame = content.document.createElement("iframe");
+ innerFrame.allow = "fullscreen";
+ innerFrame.src = `https://example.net${innerPath}`;
+ let innerLoaded = new Promise(r =>
+ innerFrame.addEventListener("load", r, { once: true })
+ );
+ middleDiv.appendChild(innerFrame);
+ await innerLoaded;
+ });
+ });
+
+ async function exitFullscreen() {
+ let warningHidden = waitForWarningState(warning, "hidden");
+ let exitPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "fullscreenchange",
+ false,
+ () => !document.fullscreenElement
+ );
+ document.getElementById("fullscreen-exit-button").click();
+ await Promise.all([exitPromise, warningHidden]);
+ }
+
+ // Step 1: Top-level (example.com) requests fullscreen on its div.
+ let warningShown = waitForWarningState(warning, "onscreen");
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.querySelector("div").requestFullscreen();
+ });
+ await warningShown;
+ is(
+ getWarningDomain(warning),
+ "example.com",
+ "Top-level fullscreen: warning shows top-level domain"
+ );
+ await exitFullscreen();
+
+ // Step 2: Middle frame (example.org) requests fullscreen on its div.
+ warningShown = waitForWarningState(warning, "onscreen");
+ await SpecialPowers.spawn(browser, [], async () => {
+ let middleFrame = content.document.querySelector("iframe");
+ middleFrame.focus();
+ await SpecialPowers.spawn(middleFrame, [], () => {
+ content.document.querySelector("div").requestFullscreen();
+ });
+ });
+ await warningShown;
+ is(
+ getWarningDomain(warning),
+ "example.com",
+ "Middle frame fullscreen: warning shows top-level domain"
+ );
+ await exitFullscreen();
+
+ // Step 3: Inner frame (example.net) requests fullscreen on an element.
+ warningShown = waitForWarningState(warning, "onscreen");
+ await SpecialPowers.spawn(browser, [], async () => {
+ let middleFrame = content.document.querySelector("iframe");
+ await SpecialPowers.spawn(middleFrame, [], async () => {
+ let innerFrame = content.document.querySelector("iframe");
+ innerFrame.focus();
+ await SpecialPowers.spawn(innerFrame, [], () => {
+ content.document.getElementById("request").click();
+ });
+ });
+ });
+ await warningShown;
+ is(
+ getWarningDomain(warning),
+ "example.com",
+ "Inner frame fullscreen: warning shows top-level domain"
+ );
+ await exitFullscreen();
+ });
+});
Loading diff…
References
On This Page