Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSpoofing issue in the DOM: Core & HTML component
ComponentCore
Bug ClassLogic Error
Tracker2021080
Fix commit1cc3c53a7515 (firefox) +203/-6
CISA KEVNot listed
CreditedFarras Givari
Disclosed2026-04-21

Changed Functions

FunctionChangeNotes
if
browser/actors/DOMFullscreenParent.sys.mjs
modified
showFullScreen
browser/base/content/browser-fullScreenAndPointerLock.js
modified
if
browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
modified
add_setup
browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
modified
add_task
browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
modified

Files Changed

  • browser/actors/DOMFullscreenParent.sys.mjs
  • browser/base/content/browser-fullScreenAndPointerLock.js
  • browser/base/content/test/fullscreen/browser.toml
  • browser/base/content/test/fullscreen/browser_fullscreen_toplevel_warning.js
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…