summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/fullscreen
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/base/content/test/fullscreen/.eslintrc.js5
-rw-r--r--browser/base/content/test/fullscreen/FullscreenFrame.jsm105
-rw-r--r--browser/base/content/test/fullscreen/browser.ini23
-rw-r--r--browser/base/content/test/fullscreen/browser_bug1557041.js49
-rw-r--r--browser/base/content/test/fullscreen/browser_bug1620341.js92
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js246
-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_permissions_prompt.js160
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js52
-rw-r--r--browser/base/content/test/fullscreen/browser_fullscreen_window_open.js48
-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.js125
-rw-r--r--browser/base/content/test/fullscreen/open_and_focus_helper.html44
15 files changed, 1084 insertions, 0 deletions
diff --git a/browser/base/content/test/fullscreen/.eslintrc.js b/browser/base/content/test/fullscreen/.eslintrc.js
new file mode 100644
index 0000000000..1779fd7f1c
--- /dev/null
+++ b/browser/base/content/test/fullscreen/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/browser-test"],
+};
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..b5b52db326
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files =
+ head.js
+ open_and_focus_helper.html
+[browser_bug1557041.js]
+skip-if = os == 'linux' # Bug 1561973
+[browser_fullscreen_permissions_prompt.js]
+skip-if = debug && os == 'mac' # Bug 1568570
+[browser_fullscreen_cross_origin.js]
+support-files = fullscreen.html fullscreen_frame.html
+[browser_bug1620341.js]
+support-files = fullscreen.html fullscreen_frame.html
+[browser_fullscreen_enterInUrlbar.js]
+skip-if = (os == 'mac') || (os == 'linux') #Bug 1648649
+[browser_fullscreen_window_open.js]
+skip-if = debug && os == 'mac' # Bug 1568570
+[browser_fullscreen_window_focus.js]
+skip-if =
+ os == 'linux' && fission # Bug 1677899
+ os == 'mac' # Bug 1568570
+ os == 'win' && fission # Bug 1677899
+[browser_fullscreen_api_fission.js]
+support-files = fullscreen.html FullscreenFrame.jsm
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..6dfa090676
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_bug1557041.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { BrowserTestUtils } = ChromeUtils.import(
+ "resource://testing-common/BrowserTestUtils.jsm"
+);
+
+// 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 identityBox = document.getElementById("identity-box");
+
+ info("Entering DOM fullscreen");
+ await changeFullscreen(browser, true);
+
+ let popupShown = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ event => event.target == document.getElementById("identity-popup")
+ );
+ let fsExit = waitForFullScreenState(browser, false);
+
+ identityBox.click();
+
+ info("Waiting for fullscreen exit and identity popup to show");
+ await Promise.all([fsExit, popupShown]);
+
+ let identityPopup = document.getElementById("identity-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..dc6a56a7d2
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_bug1620341.js
@@ -0,0 +1,92 @@
+/* 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")
+ );
+
+ // 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 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")
+ );
+
+ // 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_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..0f549e7ecf
--- /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";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
+});
+
+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_permissions_prompt.js b/browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js
new file mode 100644
index 0000000000..377cf1d655
--- /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.import(
+ "resource://testing-common/PromiseTestUtils.jsm"
+);
+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_window_focus.js b/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js
new file mode 100644
index 0000000000..c8a2ca745f
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js
@@ -0,0 +1,52 @@
+/* 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 TEST_URL =
+ "http://example.com/browser/browser/base/content/test/fullscreen/open_and_focus_helper.html";
+const IFRAME_ID = "testIframe";
+
+async function testWindowFocus(iframeID) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Calling window.open()");
+ let popup = await jsWindowOpen(tab.linkedBrowser, iframeID);
+ info("re-focusing main window");
+ await waitForFocus();
+
+ 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
+ popup.close();
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function setup() {
+ 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_parentWindowFocus() {
+ return testWindowFocus();
+});
+
+add_task(function test_iframeWindowFocus() {
+ return testWindowFocus(IFRAME_ID);
+});
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..d140b1389b
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_window_open.js
@@ -0,0 +1,48 @@
+/* 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 TEST_URL =
+ "http://example.com/browser/browser/base/content/test/fullscreen/open_and_focus_helper.html";
+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, iframeID);
+ });
+
+ // Cleanup
+ await BrowserTestUtils.closeWindow(popup);
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function setup() {
+ 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);
+});
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..b6aade2e03
--- /dev/null
+++ b/browser/base/content/test/fullscreen/head.js
@@ -0,0 +1,125 @@
+const { ContentTaskUtils } = ChromeUtils.import(
+ "resource://testing-common/ContentTaskUtils.jsm"
+);
+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, !leaveFS);
+ 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");
+ });
+}
+
+async function jsWindowOpen(browser, iframeId) {
+ let windowOpened = BrowserTestUtils.waitForNewWindow();
+ ContentTask.spawn(browser, { 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, "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..088e278965
--- /dev/null
+++ b/browser/base/content/test/fullscreen/open_and_focus_helper.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset='utf-8'>
+</head>
+<body>
+ <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.popup = window.open('https://example.com', '', 'top=0,height=1, width=300');
+ if (canReply) event.ports[0].postMessage('opened');
+ } else if(event.data === "focus") {
+ window.popup.focus();
+ if (canReply) event.ports[0].postMessage('focused');
+ }
+ }
+ window.addEventListener('message', window.onMessage);
+ </script>
+</body>
+</html>