summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/fullscreen
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/base/content/test/fullscreen
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/base/content/test/fullscreen')
-rw-r--r--browser/base/content/test/fullscreen/FullscreenFrame.jsm105
-rw-r--r--browser/base/content/test/fullscreen/browser.ini24
-rw-r--r--browser/base/content/test/fullscreen/browser_bug1557041.js47
-rw-r--r--browser/base/content/test/fullscreen/browser_bug1620341.js108
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js246
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_context_menu.js142
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_cross_origin.js64
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_enterInUrlbar.js50
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_newtab.js55
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js160
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_warning.js127
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js110
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_window_open.js70
-rw-r--r--browser/base/content/test/fullscreen/fullscreen.html12
-rw-r--r--browser/base/content/test/fullscreen/fullscreen_frame.html9
-rw-r--r--browser/base/content/test/fullscreen/head.js149
-rw-r--r--browser/base/content/test/fullscreen/open_and_focus_helper.html51
17 files changed, 1529 insertions, 0 deletions
diff --git a/browser/base/content/test/fullscreen/FullscreenFrame.jsm b/browser/base/content/test/fullscreen/FullscreenFrame.jsm
new file mode 100644
index 0000000000..28fabcd7f2
--- /dev/null
+++ b/browser/base/content/test/fullscreen/FullscreenFrame.jsm
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * test helper JSWindowActors used by the browser_fullscreen_api_fission.js test.
+ */
+
+var EXPORTED_SYMBOLS = ["FullscreenFrameChild"];
+
+class FullscreenFrameChild extends JSWindowActorChild {
+ actorCreated() {
+ this.fullscreen_events = [];
+ }
+
+ changed() {
+ return new Promise(resolve => {
+ this.contentWindow.document.addEventListener(
+ "fullscreenchange",
+ () => resolve(),
+ {
+ once: true,
+ }
+ );
+ });
+ }
+
+ requestFullscreen() {
+ let doc = this.contentWindow.document;
+ let button = doc.createElement("button");
+ doc.body.appendChild(button);
+
+ return new Promise(resolve => {
+ button.onclick = () => {
+ doc.body.requestFullscreen().then(resolve);
+ doc.body.removeChild(button);
+ };
+ button.click();
+ });
+ }
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "WaitForChange":
+ return this.changed();
+ case "ExitFullscreen":
+ return this.contentWindow.document.exitFullscreen();
+ case "RequestFullscreen":
+ this.browsingContext.isActive = true;
+ return Promise.all([this.changed(), this.requestFullscreen()]);
+ case "CreateChild":
+ let child = msg.data;
+ let iframe = this.contentWindow.document.createElement("iframe");
+ iframe.allow = child.allow_fullscreen ? "fullscreen" : "";
+ iframe.name = child.name;
+
+ let loaded = new Promise(resolve => {
+ iframe.addEventListener(
+ "load",
+ () => resolve(iframe.browsingContext),
+ { once: true }
+ );
+ });
+ iframe.src = child.url;
+ this.contentWindow.document.body.appendChild(iframe);
+ return loaded;
+ case "GetEvents":
+ return Promise.resolve(this.fullscreen_events);
+ case "ClearEvents":
+ this.fullscreen_events = [];
+ return Promise.resolve();
+ case "GetFullscreenElement":
+ let document = this.contentWindow.document;
+ let child_iframe = this.contentWindow.document.getElementsByTagName(
+ "iframe"
+ )
+ ? this.contentWindow.document.getElementsByTagName("iframe")[0]
+ : null;
+ switch (document.fullscreenElement) {
+ case null:
+ return Promise.resolve("null");
+ case document:
+ return Promise.resolve("document");
+ case document.body:
+ return Promise.resolve("body");
+ case child_iframe:
+ return Promise.resolve("child_iframe");
+ default:
+ return Promise.resolve("other");
+ }
+ }
+
+ return Promise.reject("Unexpected Message");
+ }
+
+ async handleEvent(event) {
+ switch (event.type) {
+ case "fullscreenchange":
+ this.fullscreen_events.push(true);
+ break;
+ case "fullscreenerror":
+ this.fullscreen_events.push(false);
+ break;
+ }
+ }
+}
diff --git a/browser/base/content/test/fullscreen/browser.ini b/browser/base/content/test/fullscreen/browser.ini
new file mode 100644
index 0000000000..2d5404ab73
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+support-files =
+ head.js
+ open_and_focus_helper.html
+
+[browser_bug1557041.js]
+[browser_bug1620341.js]
+support-files = fullscreen.html fullscreen_frame.html
+[browser_fullscreen_api_fission.js]
+https_first_disabled = true
+support-files = fullscreen.html FullscreenFrame.jsm
+[browser_fullscreen_context_menu.js]
+[browser_fullscreen_cross_origin.js]
+support-files = fullscreen.html fullscreen_frame.html
+[browser_fullscreen_enterInUrlbar.js]
+skip-if = (os == 'mac') || (os == 'linux') # Bug 1648649
+[browser_fullscreen_newtab.js]
+[browser_fullscreen_permissions_prompt.js]
+[browser_fullscreen_warning.js]
+support-files = fullscreen.html
+[browser_fullscreen_window_focus.js]
+skip-if = (os == 'mac') && debug # Bug 1568570
+[browser_fullscreen_window_open.js]
+skip-if = (os == 'linux') && swgl # Bug 1795491
diff --git a/browser/base/content/test/fullscreen/browser_bug1557041.js b/browser/base/content/test/fullscreen/browser_bug1557041.js
new file mode 100644
index 0000000000..363a62fe28
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_bug1557041.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_task(async function test_identityPopupCausesFSExit() {
+ let url = "https://example.com/";
+
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ BrowserTestUtils.loadURI(browser, url);
+ await loaded;
+
+ let identityPermissionBox = document.getElementById(
+ "identity-permission-box"
+ );
+
+ info("Entering DOM fullscreen");
+ await changeFullscreen(browser, true);
+
+ let popupShown = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ event => event.target == document.getElementById("permission-popup")
+ );
+ let fsExit = waitForFullScreenState(browser, false);
+
+ identityPermissionBox.click();
+
+ info("Waiting for fullscreen exit and permission popup to show");
+ await Promise.all([fsExit, popupShown]);
+
+ let identityPopup = document.getElementById("permission-popup");
+ ok(
+ identityPopup.hasAttribute("panelopen"),
+ "Identity popup should be open"
+ );
+ ok(!window.fullScreen, "Should not be in full-screen");
+ });
+});
diff --git a/browser/base/content/test/fullscreen/browser_bug1620341.js b/browser/base/content/test/fullscreen/browser_bug1620341.js
new file mode 100644
index 0000000000..5f765b81e8
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_bug1620341.js
@@ -0,0 +1,108 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const tab1URL = `data:text/html,
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>First tab to be loaded</title>
+ </head>
+ <body>
+ <button>JUST A BUTTON</button>
+ </body>
+ </html>`;
+
+const ORIGIN =
+ "https://example.com/browser/browser/base/content/test/fullscreen/fullscreen_frame.html";
+
+add_task(async function test_fullscreen_cross_origin() {
+ async function requestFullscreenThenCloseTab() {
+ await BrowserTestUtils.withNewTab(ORIGIN, async function(browser) {
+ info("Start fullscreen on iframe frameAllowed");
+
+ // Make sure there is no attribute "inDOMFullscreen" before requesting fullscreen.
+ await TestUtils.waitForCondition(
+ () => !document.documentElement.hasAttribute("inDOMFullscreen")
+ );
+
+ let tabbrowser = browser.ownerDocument.querySelector("#tabbrowser-tabs");
+ ok(
+ !tabbrowser.hasAttribute("closebuttons"),
+ "Close buttons should be visible on every tab"
+ );
+
+ // Request fullscreen from iframe
+ await SpecialPowers.spawn(browser, [], async function() {
+ let frame = content.document.getElementById("frameAllowed");
+ frame.focus();
+ await SpecialPowers.spawn(frame, [], async () => {
+ let frameDoc = content.document;
+ const waitForFullscreen = new Promise(resolve => {
+ const message = "fullscreenchange";
+ function handler(evt) {
+ frameDoc.removeEventListener(message, handler);
+ Assert.equal(evt.type, message, `Request should be allowed`);
+ resolve();
+ }
+ frameDoc.addEventListener(message, handler);
+ });
+
+ frameDoc.getElementById("request").click();
+ await waitForFullscreen;
+ });
+ });
+
+ // Make sure there is attribute "inDOMFullscreen" after requesting fullscreen.
+ await TestUtils.waitForCondition(() =>
+ document.documentElement.hasAttribute("inDOMFullscreen")
+ );
+
+ await TestUtils.waitForCondition(
+ () => tabbrowser.hasAttribute("closebuttons"),
+ "Close buttons should be visible only on the active tab (tabs have width=0 so closebuttons gets set on them)"
+ );
+ });
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ });
+
+ // Open a tab with tab1URL.
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ tab1URL,
+ true
+ );
+
+ // 1. Open another tab and load a page with two iframes.
+ // 2. Request fullscreen from an iframe which is in a different origin.
+ // 3. Close the tab after receiving "fullscreenchange" message.
+ // Note that we don't do "doc.exitFullscreen()" before closing the tab
+ // on purpose.
+ await requestFullscreenThenCloseTab();
+
+ // Wait until attribute "inDOMFullscreen" is removed.
+ await TestUtils.waitForCondition(
+ () => !document.documentElement.hasAttribute("inDOMFullscreen")
+ );
+
+ await TestUtils.waitForCondition(
+ () => !gBrowser.tabContainer.hasAttribute("closebuttons"),
+ "Close buttons should come back to every tab"
+ );
+
+ // Remove the remaining tab and leave the test.
+ let tabClosed = BrowserTestUtils.waitForTabClosing(tab1);
+ BrowserTestUtils.removeTab(tab1);
+ await tabClosed;
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js b/browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js
new file mode 100644
index 0000000000..4207b2b521
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js
@@ -0,0 +1,246 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This test checks that `document.fullscreenElement` is set correctly and
+ * proper fullscreenchange events fire when an element inside of a
+ * multi-origin tree of iframes calls `requestFullscreen()`. It is designed
+ * to make sure the fullscreen API is working properly in fission when the
+ * frame tree spans multiple processes.
+ *
+ * A similarly purposed Web Platform Test exists, but at the time of writing
+ * is manual, so it cannot be run in CI:
+ * `element-request-fullscreen-cross-origin-manual.sub.html`
+ */
+
+"use strict";
+
+const actorModuleURI = getRootDirectory(gTestPath) + "FullscreenFrame.jsm";
+const actorName = "FullscreenFrame";
+
+const fullscreenPath =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", "") +
+ "fullscreen.html";
+
+const fullscreenTarget = "D";
+// TOP
+// | \
+// A B
+// |
+// C
+// |
+// D
+// |
+// E
+const frameTree = {
+ name: "TOP",
+ url: `http://example.com${fullscreenPath}`,
+ allow_fullscreen: true,
+ children: [
+ {
+ name: "A",
+ url: `http://example.org${fullscreenPath}`,
+ allow_fullscreen: true,
+ children: [
+ {
+ name: "C",
+ url: `http://example.com${fullscreenPath}`,
+ allow_fullscreen: true,
+ children: [
+ {
+ name: "D",
+ url: `http://example.com${fullscreenPath}?different-uri=1`,
+ allow_fullscreen: true,
+ children: [
+ {
+ name: "E",
+ url: `http://example.org${fullscreenPath}`,
+ allow_fullscreen: true,
+ children: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: "B",
+ url: `http://example.net${fullscreenPath}`,
+ allow_fullscreen: true,
+ children: [],
+ },
+ ],
+};
+
+add_task(async function test_fullscreen_api_cross_origin_tree() {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+
+ // Register a custom window actor to handle tracking events
+ // and constructing subframes
+ ChromeUtils.registerWindowActor(actorName, {
+ child: {
+ moduleURI: actorModuleURI,
+ events: {
+ fullscreenchange: { mozSystemGroup: true, capture: true },
+ fullscreenerror: { mozSystemGroup: true, capture: true },
+ },
+ },
+ allFrames: true,
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: frameTree.url,
+ });
+
+ let frames = new Map();
+ async function construct_frame_children(browsingContext, tree) {
+ let actor = browsingContext.currentWindowGlobal.getActor(actorName);
+ frames.set(tree.name, {
+ browsingContext,
+ actor,
+ });
+
+ for (let child of tree.children) {
+ // Create the child IFrame and wait for it to load.
+ let childBC = await actor.sendQuery("CreateChild", child);
+ await construct_frame_children(childBC, child);
+ }
+ }
+
+ await construct_frame_children(tab.linkedBrowser.browsingContext, frameTree);
+
+ async function check_events(expected_events) {
+ for (let [name, expected] of expected_events) {
+ let actor = frames.get(name).actor;
+
+ // Each content process fires the fullscreenchange
+ // event independently and in parallel making it
+ // possible for the promises returned by
+ // `requestFullscreen` or `exitFullscreen` to
+ // resolve before all events have fired. We wait
+ // for the number of events to match before
+ // continuing to ensure we don't miss an expected
+ // event that hasn't fired yet.
+ let events;
+ await TestUtils.waitForCondition(async () => {
+ events = await actor.sendQuery("GetEvents");
+ return events.length == expected.length;
+ }, `Waiting for number of events to match`);
+
+ Assert.equal(events.length, expected.length, "Number of events equal");
+ events.forEach((value, i) => {
+ Assert.equal(value, expected[i], "Event type matches");
+ });
+ }
+ }
+
+ async function check_fullscreenElement(expected_elements) {
+ for (let [name, expected] of expected_elements) {
+ let element = await frames
+ .get(name)
+ .actor.sendQuery("GetFullscreenElement");
+ Assert.equal(element, expected, "The fullScreenElement matches");
+ }
+ }
+
+ // Trigger fullscreen from the target frame.
+ let target = frames.get(fullscreenTarget);
+ await target.actor.sendQuery("RequestFullscreen");
+ // true is fullscreenchange and false is fullscreenerror.
+ await check_events(
+ new Map([
+ ["TOP", [true]],
+ ["A", [true]],
+ ["B", []],
+ ["C", [true]],
+ ["D", [true]],
+ ["E", []],
+ ])
+ );
+ await check_fullscreenElement(
+ new Map([
+ ["TOP", "child_iframe"],
+ ["A", "child_iframe"],
+ ["B", "null"],
+ ["C", "child_iframe"],
+ ["D", "body"],
+ ["E", "null"],
+ ])
+ );
+
+ await target.actor.sendQuery("ExitFullscreen");
+ // fullscreenchange should have fired on exit as well.
+ // true is fullscreenchange and false is fullscreenerror.
+ await check_events(
+ new Map([
+ ["TOP", [true, true]],
+ ["A", [true, true]],
+ ["B", []],
+ ["C", [true, true]],
+ ["D", [true, true]],
+ ["E", []],
+ ])
+ );
+ await check_fullscreenElement(
+ new Map([
+ ["TOP", "null"],
+ ["A", "null"],
+ ["B", "null"],
+ ["C", "null"],
+ ["D", "null"],
+ ["E", "null"],
+ ])
+ );
+
+ // Clear previous events before testing exiting fullscreen with ESC.
+ for (const frame of frames.values()) {
+ frame.actor.sendQuery("ClearEvents");
+ }
+ await target.actor.sendQuery("RequestFullscreen");
+
+ // Escape should cause the proper events to fire and
+ // document.fullscreenElement should be cleared.
+ let finished_exiting = target.actor.sendQuery("WaitForChange");
+ EventUtils.sendKey("ESCAPE");
+ await finished_exiting;
+ // true is fullscreenchange and false is fullscreenerror.
+ await check_events(
+ new Map([
+ ["TOP", [true, true]],
+ ["A", [true, true]],
+ ["B", []],
+ ["C", [true, true]],
+ ["D", [true, true]],
+ ["E", []],
+ ])
+ );
+ await check_fullscreenElement(
+ new Map([
+ ["TOP", "null"],
+ ["A", "null"],
+ ["B", "null"],
+ ["C", "null"],
+ ["D", "null"],
+ ["E", "null"],
+ ])
+ );
+
+ // Remove the tests custom window actor.
+ ChromeUtils.unregisterWindowActor("FullscreenFrame");
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_context_menu.js b/browser/base/content/test/fullscreen/browser_fullscreen_context_menu.js
new file mode 100644
index 0000000000..ec874f1a3f
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_context_menu.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function openContextMenu(itemElement, win = window) {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ itemElement.ownerDocument,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ itemElement,
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ win
+ );
+ let { target } = await popupShownPromise;
+ return target;
+}
+
+async function testContextMenu() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ let panelUIMenuButton = document.getElementById("PanelUI-menu-button");
+ let contextMenu = await openContextMenu(panelUIMenuButton);
+ let array1 = AppConstants.MENUBAR_CAN_AUTOHIDE
+ ? [
+ ".customize-context-moveToPanel",
+ ".customize-context-removeFromToolbar",
+ "#toolbarItemsMenuSeparator",
+ "#toggle_toolbar-menubar",
+ "#toggle_PersonalToolbar",
+ "#viewToolbarsMenuSeparator",
+ ".viewCustomizeToolbar",
+ ]
+ : [
+ ".customize-context-moveToPanel",
+ ".customize-context-removeFromToolbar",
+ "#toolbarItemsMenuSeparator",
+ "#toggle_PersonalToolbar",
+ "#viewToolbarsMenuSeparator",
+ ".viewCustomizeToolbar",
+ ];
+ let result1 = verifyContextMenu(contextMenu, array1);
+ ok(!result1, "Expected no errors verifying context menu items");
+ contextMenu.hidePopup();
+ let onFullscreen = Promise.all([
+ BrowserTestUtils.waitForEvent(window, "fullscreen"),
+ BrowserTestUtils.waitForEvent(
+ window,
+ "sizemodechange",
+ false,
+ e => window.fullScreen
+ ),
+ BrowserTestUtils.waitForPopupEvent(contextMenu, "hidden"),
+ ]);
+ document.getElementById("View:FullScreen").doCommand();
+ contextMenu.hidePopup();
+ info("waiting for fullscreen");
+ await onFullscreen;
+ // make sure the toolbox is visible if it's autohidden
+ document.getElementById("Browser:OpenLocation").doCommand();
+ info("trigger the context menu");
+ let contextMenu2 = await openContextMenu(panelUIMenuButton);
+ info("context menu should be open, verify its menu items");
+ let array2 = AppConstants.MENUBAR_CAN_AUTOHIDE
+ ? [
+ ".customize-context-moveToPanel",
+ ".customize-context-removeFromToolbar",
+ "#toolbarItemsMenuSeparator",
+ "#toggle_toolbar-menubar",
+ "#toggle_PersonalToolbar",
+ "#viewToolbarsMenuSeparator",
+ ".viewCustomizeToolbar",
+ `menuseparator[contexttype="fullscreen"]`,
+ `.fullscreen-context-autohide`,
+ `menuitem[contexttype="fullscreen"]`,
+ ]
+ : [
+ ".customize-context-moveToPanel",
+ ".customize-context-removeFromToolbar",
+ "#toolbarItemsMenuSeparator",
+ "#toggle_PersonalToolbar",
+ "#viewToolbarsMenuSeparator",
+ ".viewCustomizeToolbar",
+ `menuseparator[contexttype="fullscreen"]`,
+ `.fullscreen-context-autohide`,
+ `menuitem[contexttype="fullscreen"]`,
+ ];
+ let result2 = verifyContextMenu(contextMenu2, array2);
+ ok(!result2, "Expected no errors verifying context menu items");
+ let onExitFullscreen = Promise.all([
+ BrowserTestUtils.waitForEvent(window, "fullscreen"),
+ BrowserTestUtils.waitForEvent(
+ window,
+ "sizemodechange",
+ false,
+ e => !window.fullScreen
+ ),
+ BrowserTestUtils.waitForPopupEvent(contextMenu2, "hidden"),
+ ]);
+ document.getElementById("View:FullScreen").doCommand();
+ contextMenu2.hidePopup();
+ await onExitFullscreen;
+ });
+}
+
+function verifyContextMenu(contextMenu, itemSelectors) {
+ // Ignore hidden nodes
+ let items = Array.from(contextMenu.children).filter(n =>
+ BrowserTestUtils.is_visible(n)
+ );
+ let menuAsText = items
+ .map(n => {
+ return n.nodeName == "menuseparator"
+ ? "---"
+ : `${n.label} (${n.command})`;
+ })
+ .join("\n");
+ info("Got actual context menu items: \n" + menuAsText);
+
+ try {
+ is(
+ items.length,
+ itemSelectors.length,
+ "Context menu has the expected number of items"
+ );
+ for (let i = 0; i < items.length; i++) {
+ let selector = itemSelectors[i];
+ ok(
+ items[i].matches(selector),
+ `Item at ${i} matches expected selector: ${selector}`
+ );
+ }
+ } catch (ex) {
+ return ex;
+ }
+ return null;
+}
+
+add_task(testContextMenu);
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_cross_origin.js b/browser/base/content/test/fullscreen/browser_fullscreen_cross_origin.js
new file mode 100644
index 0000000000..b1199557b3
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_cross_origin.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ORIGIN =
+ "https://example.com/browser/browser/base/content/test/fullscreen/fullscreen_frame.html";
+
+add_task(async function test_fullscreen_cross_origin() {
+ async function requestFullscreen(aAllow, aExpect) {
+ await BrowserTestUtils.withNewTab(ORIGIN, async function(browser) {
+ const iframeId = aExpect == "allowed" ? "frameAllowed" : "frameDenied";
+
+ info("Start fullscreen on iframe " + iframeId);
+ await SpecialPowers.spawn(
+ browser,
+ [{ aExpect, iframeId }],
+ async function(args) {
+ let frame = content.document.getElementById(args.iframeId);
+ frame.focus();
+ await SpecialPowers.spawn(frame, [args.aExpect], async expect => {
+ let frameDoc = content.document;
+ const waitForFullscreen = new Promise(resolve => {
+ const message =
+ expect == "allowed" ? "fullscreenchange" : "fullscreenerror";
+ function handler(evt) {
+ frameDoc.removeEventListener(message, handler);
+ Assert.equal(evt.type, message, `Request should be ${expect}`);
+ frameDoc.exitFullscreen();
+ resolve();
+ }
+ frameDoc.addEventListener(message, handler);
+ });
+ frameDoc.getElementById("request").click();
+ await waitForFullscreen;
+ });
+ }
+ );
+
+ if (aExpect == "allowed") {
+ waitForFullScreenState(browser, false);
+ }
+ });
+ }
+
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+
+ await requestFullscreen(undefined, "denied");
+ await requestFullscreen("fullscreen", "allowed");
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_enterInUrlbar.js b/browser/base/content/test/fullscreen/browser_fullscreen_enterInUrlbar.js
new file mode 100644
index 0000000000..914e2b262a
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_enterInUrlbar.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test makes sure that when the user presses enter in the urlbar in full
+// screen, the toolbars are hidden. This should not be run on macOS because we
+// don't hide the toolbars there.
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ // Do the View:FullScreen command and wait for the transition.
+ let onFullscreen = BrowserTestUtils.waitForEvent(window, "fullscreen");
+ document.getElementById("View:FullScreen").doCommand();
+ await onFullscreen;
+
+ // Do the Browser:OpenLocation command to show the nav toolbox and focus
+ // the urlbar.
+ let onToolboxShown = TestUtils.topicObserved(
+ "fullscreen-nav-toolbox",
+ (subject, data) => data == "shown"
+ );
+ document.getElementById("Browser:OpenLocation").doCommand();
+ info("Waiting for the nav toolbox to be shown");
+ await onToolboxShown;
+
+ // Enter a URL.
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: "http://example.com/",
+ waitForFocus: SimpleTest.waitForFocus,
+ fireInputEvent: true,
+ });
+
+ // Press enter and wait for the nav toolbox to be hidden.
+ let onToolboxHidden = TestUtils.topicObserved(
+ "fullscreen-nav-toolbox",
+ (subject, data) => data == "hidden"
+ );
+ EventUtils.synthesizeKey("KEY_Enter");
+ info("Waiting for the nav toolbox to be hidden");
+ await onToolboxHidden;
+
+ Assert.ok(true, "Nav toolbox hidden");
+ });
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_newtab.js b/browser/base/content/test/fullscreen/browser_fullscreen_newtab.js
new file mode 100644
index 0000000000..d5a74a0aa3
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_newtab.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test verifies that when in fullscreen mode, and a new tab is opened,
+// fullscreen mode is exited and the url bar is focused.
+add_task(async function test_fullscreen_display_none() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html"
+ );
+
+ let fullScreenEntered = BrowserTestUtils.waitForEvent(
+ document,
+ "fullscreenchange",
+ false,
+ () => document.fullscreenElement
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("request").click();
+ });
+
+ await fullScreenEntered;
+
+ let fullScreenExited = BrowserTestUtils.waitForEvent(
+ document,
+ "fullscreenchange",
+ false,
+ () => !document.fullscreenElement
+ );
+
+ let focusPromise = BrowserTestUtils.waitForEvent(window, "focus");
+ EventUtils.synthesizeKey("T", { accelKey: true });
+ await focusPromise;
+
+ is(
+ document.activeElement,
+ gURLBar.inputField,
+ "url bar is focused after new tab opened"
+ );
+
+ await fullScreenExited;
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js b/browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js
new file mode 100644
index 0000000000..82f0c97631
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+PromiseTestUtils.allowMatchingRejectionsGlobally(/Not in fullscreen mode/);
+
+SimpleTest.requestCompleteLog();
+
+async function requestNotificationPermission(browser) {
+ return SpecialPowers.spawn(browser, [], () => {
+ return content.Notification.requestPermission();
+ });
+}
+
+async function requestCameraPermission(browser) {
+ return SpecialPowers.spawn(browser, [], () =>
+ content.navigator.mediaDevices
+ .getUserMedia({ video: true, fake: true })
+ .then(
+ () => true,
+ () => false
+ )
+ );
+}
+
+add_task(async function test_fullscreen_closes_permissionui_prompt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.webnotifications.requireuserinteraction", false],
+ ["permissions.fullscreen.allowed", false],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ let browser = tab.linkedBrowser;
+
+ let popupShown, requestResult, popupHidden;
+
+ popupShown = BrowserTestUtils.waitForEvent(
+ window.PopupNotifications.panel,
+ "popupshown"
+ );
+
+ info("Requesting notification permission");
+ requestResult = requestNotificationPermission(browser);
+ await popupShown;
+
+ info("Entering DOM full-screen");
+ popupHidden = BrowserTestUtils.waitForEvent(
+ window.PopupNotifications.panel,
+ "popuphidden"
+ );
+
+ await changeFullscreen(browser, true);
+
+ await popupHidden;
+
+ is(
+ await requestResult,
+ "default",
+ "Expect permission request to be cancelled"
+ );
+
+ await changeFullscreen(browser, false);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_fullscreen_closes_webrtc_permission_prompt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.navigator.permission.fake", true],
+ ["media.navigator.permission.force", true],
+ ["permissions.fullscreen.allowed", false],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ let browser = tab.linkedBrowser;
+ let popupShown, requestResult, popupHidden;
+
+ popupShown = BrowserTestUtils.waitForEvent(
+ window.PopupNotifications.panel,
+ "popupshown"
+ );
+
+ info("Requesting camera permission");
+ requestResult = requestCameraPermission(browser);
+
+ await popupShown;
+
+ info("Entering DOM full-screen");
+ popupHidden = BrowserTestUtils.waitForEvent(
+ window.PopupNotifications.panel,
+ "popuphidden"
+ );
+ await changeFullscreen(browser, true);
+
+ await popupHidden;
+
+ is(
+ await requestResult,
+ false,
+ "Expect webrtc permission request to be cancelled"
+ );
+
+ await changeFullscreen(browser, false);
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_permission_prompt_closes_fullscreen() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.webnotifications.requireuserinteraction", false],
+ ["permissions.fullscreen.allowed", false],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com"
+ );
+ let browser = tab.linkedBrowser;
+ info("Entering DOM full-screen");
+ await changeFullscreen(browser, true);
+
+ let popupShown = BrowserTestUtils.waitForEvent(
+ window.PopupNotifications.panel,
+ "popupshown"
+ );
+ let fullScreenExit = waitForFullScreenState(browser, false);
+
+ info("Requesting notification permission");
+ requestNotificationPermission(browser).catch(() => {});
+ await popupShown;
+
+ info("Waiting for full-screen exit");
+ await fullScreenExit;
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_warning.js b/browser/base/content/test/fullscreen/browser_fullscreen_warning.js
new file mode 100644
index 0000000000..210f65ee1c
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_warning.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_fullscreen_display_none() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Fullscreen Test</title>
+ </head>
+ <body id="body">
+ <iframe
+ src="https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html"
+ hidden
+ allowfullscreen></iframe>
+ </body>
+ </html>`,
+ },
+ async function(browser) {
+ let warning = document.getElementById("fullscreen-warning");
+ let warningShownPromise = BrowserTestUtils.waitForAttribute(
+ "onscreen",
+ warning,
+ "true"
+ );
+ // Enter fullscreen
+ await SpecialPowers.spawn(browser, [], async () => {
+ let frame = content.document.querySelector("iframe");
+ frame.focus();
+ await SpecialPowers.spawn(frame, [], () => {
+ content.document.getElementById("request").click();
+ });
+ });
+ await warningShownPromise;
+ ok(true, "Fullscreen warning shown");
+ // Exit fullscreen
+ let exitFullscreenPromise = BrowserTestUtils.waitForEvent(
+ document,
+ "fullscreenchange",
+ false,
+ () => !document.fullscreenElement
+ );
+ document.getElementById("fullscreen-exit-button").click();
+ await exitFullscreenPromise;
+ }
+ );
+});
+
+add_task(async function test_fullscreen_pointerlock_conflict() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ let fsWarning = document.getElementById("fullscreen-warning");
+ let plWarning = document.getElementById("pointerlock-warning");
+
+ is(
+ fsWarning.getAttribute("onscreen"),
+ null,
+ "Should not show full screen warning initially."
+ );
+ is(
+ plWarning.getAttribute("onscreen"),
+ null,
+ "Should not show pointer lock warning initially."
+ );
+
+ let fsWarningShownPromise = BrowserTestUtils.waitForAttribute(
+ "onscreen",
+ fsWarning,
+ "true"
+ );
+
+ info("Entering full screen and pointer lock.");
+ await SpecialPowers.spawn(browser, [], async () => {
+ await content.document.body.requestFullscreen();
+ await content.document.body.requestPointerLock();
+ });
+
+ await fsWarningShownPromise;
+ is(
+ fsWarning.getAttribute("onscreen"),
+ "true",
+ "Should show full screen warning."
+ );
+ is(
+ plWarning.getAttribute("onscreen"),
+ null,
+ "Should not show pointer lock warning."
+ );
+
+ info("Exiting pointerlock");
+ await SpecialPowers.spawn(browser, [], async () => {
+ await content.document.exitPointerLock();
+ });
+
+ is(
+ fsWarning.getAttribute("onscreen"),
+ "true",
+ "Should still show full screen warning."
+ );
+ is(
+ plWarning.getAttribute("onscreen"),
+ null,
+ "Should not show pointer lock warning."
+ );
+
+ // Cleanup
+ await document.exitFullscreen();
+ });
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js b/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js
new file mode 100644
index 0000000000..ac53cac54c
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function pause() {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ return new Promise(resolve => setTimeout(resolve, 500));
+}
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+const IFRAME_ID = "testIframe";
+
+async function testWindowFocus(isPopup, iframeID) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Calling window.open()");
+ let openedWindow = await jsWindowOpen(tab.linkedBrowser, isPopup, iframeID);
+ info("Letting OOP focus to stabilize");
+ await pause(); // Bug 1719659 for proper fix
+ info("re-focusing main window");
+ await waitForFocus(tab.linkedBrowser);
+
+ info("Entering full-screen");
+ await changeFullscreen(tab.linkedBrowser, true);
+
+ await testExpectFullScreenExit(tab.linkedBrowser, true, async () => {
+ info("Calling window.focus()");
+ await jsWindowFocus(tab.linkedBrowser, iframeID);
+ });
+
+ // Cleanup
+ if (isPopup) {
+ openedWindow.close();
+ } else {
+ BrowserTestUtils.removeTab(openedWindow);
+ }
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testWindowElementFocus(isPopup) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Calling window.open()");
+ let openedWindow = await jsWindowOpen(tab.linkedBrowser, isPopup);
+ info("Letting OOP focus to stabilize");
+ await pause(); // Bug 1719659 for proper fix
+ info("re-focusing main window");
+ await waitForFocus(tab.linkedBrowser);
+
+ info("Entering full-screen");
+ await changeFullscreen(tab.linkedBrowser, true);
+
+ await testExpectFullScreenExit(tab.linkedBrowser, false, async () => {
+ info("Calling element.focus() on popup");
+ await ContentTask.spawn(tab.linkedBrowser, {}, async args => {
+ await content.wrappedJSObject.sendMessage(
+ content.wrappedJSObject.openedWindow,
+ "elementfocus"
+ );
+ });
+ });
+
+ // Cleanup
+ await changeFullscreen(tab.linkedBrowser, false);
+ if (isPopup) {
+ openedWindow.close();
+ } else {
+ BrowserTestUtils.removeTab(openedWindow);
+ }
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_setup(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.disable_open_during_load", false], // Allow window.focus calls without user interaction
+ ["browser.link.open_newwindow.disabled_in_fullscreen", false],
+ ],
+ });
+});
+
+add_task(function test_popupWindowFocus() {
+ return testWindowFocus(true);
+});
+
+add_task(function test_iframePopupWindowFocus() {
+ return testWindowFocus(true, IFRAME_ID);
+});
+
+add_task(function test_popupWindowElementFocus() {
+ return testWindowElementFocus(true);
+});
+
+add_task(function test_backgroundTabFocus() {
+ return testWindowFocus(false);
+});
+
+add_task(function test_iframebackgroundTabFocus() {
+ return testWindowFocus(false, IFRAME_ID);
+});
+
+add_task(function test_backgroundTabElementFocus() {
+ return testWindowElementFocus(false);
+});
diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_window_open.js b/browser/base/content/test/fullscreen/browser_fullscreen_window_open.js
new file mode 100644
index 0000000000..b26527e917
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_window_open.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+SimpleTest.requestLongerTimeout(2);
+
+const IFRAME_ID = "testIframe";
+
+async function testWindowOpen(iframeID) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ info("Entering full-screen");
+ await changeFullscreen(tab.linkedBrowser, true);
+
+ let popup;
+ await testExpectFullScreenExit(tab.linkedBrowser, true, async () => {
+ info("Calling window.open()");
+ popup = await jsWindowOpen(tab.linkedBrowser, true, iframeID);
+ });
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(popup);
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testWindowOpenExistingWindow() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+ let popup = await jsWindowOpen(tab.linkedBrowser, true);
+
+ info("re-focusing main window");
+ await waitForFocus(tab.linkedBrowser);
+
+ info("Entering full-screen");
+ await changeFullscreen(tab.linkedBrowser, true);
+
+ await testExpectFullScreenExit(tab.linkedBrowser, true, async () => {
+ info("Calling window.open() again should reuse the existing window");
+ jsWindowOpen(tab.linkedBrowser, true);
+ });
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(popup);
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_setup(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.disable_open_during_load", false], // Allow window.open calls without user interaction
+ ["browser.link.open_newwindow.disabled_in_fullscreen", false],
+ ],
+ });
+});
+
+add_task(function test_parentWindowOpen() {
+ return testWindowOpen();
+});
+
+add_task(function test_iframeWindowOpen() {
+ return testWindowOpen(IFRAME_ID);
+});
+
+add_task(function test_parentWindowOpenExistWindow() {
+ return testWindowOpenExistingWindow();
+});
diff --git a/browser/base/content/test/fullscreen/fullscreen.html b/browser/base/content/test/fullscreen/fullscreen.html
new file mode 100644
index 0000000000..8b4289bb36
--- /dev/null
+++ b/browser/base/content/test/fullscreen/fullscreen.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+function requestFScreen() {
+ document.body.requestFullscreen();
+}
+</script>
+<body>
+<button id="request" onclick="requestFScreen()"> Fullscreen </button>
+<button id="focus"> Fullscreen </button>
+</body>
+</html>
diff --git a/browser/base/content/test/fullscreen/fullscreen_frame.html b/browser/base/content/test/fullscreen/fullscreen_frame.html
new file mode 100644
index 0000000000..ca1b1a4dd8
--- /dev/null
+++ b/browser/base/content/test/fullscreen/fullscreen_frame.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <iframe id="frameAllowed"
+ src="https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html"
+ allowfullscreen></iframe>
+ <iframe id="frameDenied" src="https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/fullscreen/head.js b/browser/base/content/test/fullscreen/head.js
new file mode 100644
index 0000000000..d25c8d63d4
--- /dev/null
+++ b/browser/base/content/test/fullscreen/head.js
@@ -0,0 +1,149 @@
+const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+);
+
+const TEST_URL =
+ "https://example.com/browser/browser/base/content/test/fullscreen/open_and_focus_helper.html";
+
+function waitForFullScreenState(browser, state) {
+ return new Promise(resolve => {
+ let eventReceived = false;
+
+ let observe = (subject, topic, data) => {
+ if (!eventReceived) {
+ return;
+ }
+ Services.obs.removeObserver(observe, "fullscreen-painted");
+ resolve();
+ };
+ Services.obs.addObserver(observe, "fullscreen-painted");
+
+ browser.ownerGlobal.addEventListener(
+ `MozDOMFullscreen:${state ? "Entered" : "Exited"}`,
+ () => {
+ eventReceived = true;
+ },
+ { once: true }
+ );
+ });
+}
+
+/**
+ * Spawns content task in browser to enter / leave fullscreen
+ * @param browser - Browser to use for JS fullscreen requests
+ * @param {Boolean} fullscreenState - true to enter fullscreen, false to leave
+ * @returns {Promise} - Resolves once fullscreen change is applied
+ */
+async function changeFullscreen(browser, fullScreenState) {
+ await new Promise(resolve =>
+ SimpleTest.waitForFocus(resolve, browser.ownerGlobal)
+ );
+ let fullScreenChange = waitForFullScreenState(browser, fullScreenState);
+ SpecialPowers.spawn(browser, [fullScreenState], async state => {
+ // Wait for document focus before requesting full-screen
+ await ContentTaskUtils.waitForCondition(
+ () => content.browsingContext.isActive && content.document.hasFocus(),
+ "Waiting for document focus"
+ );
+ if (state) {
+ content.document.body.requestFullscreen();
+ } else {
+ content.document.exitFullscreen();
+ }
+ });
+ return fullScreenChange;
+}
+
+async function testExpectFullScreenExit(browser, leaveFS, action) {
+ let fsPromise = waitForFullScreenState(browser, false);
+ if (leaveFS) {
+ if (action) {
+ await action();
+ }
+ await fsPromise;
+ ok(true, "Should leave full-screen");
+ } else {
+ if (action) {
+ await action();
+ }
+ let result = await Promise.race([
+ fsPromise,
+ new Promise(resolve => {
+ SimpleTest.requestFlakyTimeout("Wait for failure condition");
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(() => resolve(true), 2500);
+ }),
+ ]);
+ ok(result, "Should not leave full-screen");
+ }
+}
+
+function jsWindowFocus(browser, iframeId) {
+ return ContentTask.spawn(browser, { iframeId }, async args => {
+ let destWin = content;
+ if (args.iframeId) {
+ let iframe = content.document.getElementById(args.iframeId);
+ if (!iframe) {
+ throw new Error("iframe not set");
+ }
+ destWin = iframe.contentWindow;
+ }
+ await content.wrappedJSObject.sendMessage(destWin, "focus");
+ });
+}
+
+function jsElementFocus(browser, iframeId) {
+ return ContentTask.spawn(browser, { iframeId }, async args => {
+ let destWin = content;
+ if (args.iframeId) {
+ let iframe = content.document.getElementById(args.iframeId);
+ if (!iframe) {
+ throw new Error("iframe not set");
+ }
+ destWin = iframe.contentWindow;
+ }
+ await content.wrappedJSObject.sendMessage(destWin, "elementfocus");
+ });
+}
+
+async function jsWindowOpen(browser, isPopup, iframeId) {
+ //let windowOpened = BrowserTestUtils.waitForNewWindow();
+ let windowOpened = isPopup
+ ? BrowserTestUtils.waitForNewWindow({ url: TEST_URL })
+ : BrowserTestUtils.waitForNewTab(gBrowser, TEST_URL, true);
+ ContentTask.spawn(browser, { isPopup, iframeId }, async args => {
+ let destWin = content;
+ if (args.iframeId) {
+ // Create a cross origin iframe
+ destWin = (
+ await content.wrappedJSObject.createIframe(args.iframeId, true)
+ ).contentWindow;
+ }
+ // Send message to either the iframe or the current page to open a popup
+ await content.wrappedJSObject.sendMessage(
+ destWin,
+ args.isPopup ? "openpopup" : "open"
+ );
+ });
+ return windowOpened;
+}
+
+function waitForFocus(...args) {
+ return new Promise(resolve => SimpleTest.waitForFocus(resolve, ...args));
+}
+
+function waitForBrowserWindowActive(win) {
+ return new Promise(resolve => {
+ if (Services.focus.activeWindow == win) {
+ resolve();
+ } else {
+ win.addEventListener(
+ "activate",
+ () => {
+ resolve();
+ },
+ { once: true }
+ );
+ }
+ });
+}
diff --git a/browser/base/content/test/fullscreen/open_and_focus_helper.html b/browser/base/content/test/fullscreen/open_and_focus_helper.html
new file mode 100644
index 0000000000..27a1c997c6
--- /dev/null
+++ b/browser/base/content/test/fullscreen/open_and_focus_helper.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset='utf-8'>
+</head>
+<body>
+ <input></input>
+ <script>
+ const MY_ORIGIN = window.location.origin;
+ const CROSS_ORIGIN = "https://example.org";
+
+ // Creates an iframe with message channel to trigger window open and focus
+ window.createIframe = function(id, crossOrigin = false) {
+ return new Promise(resolve => {
+ const origin = crossOrigin ? CROSS_ORIGIN : MY_ORIGIN;
+ let iframe = document.createElement("iframe");
+ iframe.id = id;
+ iframe.src = origin + window.location.pathname;
+ iframe.onload = () => resolve(iframe);
+ document.body.appendChild(iframe);
+ });
+ }
+
+ window.sendMessage = function(destWin, msg) {
+ return new Promise(resolve => {
+ let channel = new MessageChannel();
+ channel.port1.onmessage = resolve;
+ destWin.postMessage(msg, "*", [channel.port2]);
+ });
+ }
+
+ window.onMessage = function(event) {
+ let canReply = event.ports && !!event.ports.length;
+ if(event.data === "open") {
+ window.openedWindow = window.open('https://example.com' + window.location.pathname);
+ if (canReply) event.ports[0].postMessage('opened');
+ } else if(event.data === "openpopup") {
+ window.openedWindow = window.open('https://example.com' + window.location.pathname, 'test', 'top=0,height=1, width=300');
+ if (canReply) event.ports[0].postMessage('popupopened');
+ } else if(event.data === "focus") {
+ window.openedWindow.focus();
+ if (canReply) event.ports[0].postMessage('focused');
+ } else if(event.data === "elementfocus") {
+ document.querySelector("input").focus();
+ if (canReply) event.ports[0].postMessage('elementfocused');
+ }
+ }
+ window.addEventListener('message', window.onMessage);
+ </script>
+</body>
+</html>