summaryrefslogtreecommitdiffstats
path: root/dom/base/test/fullscreen
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/base/test/fullscreen
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml108
-rw-r--r--dom/base/test/fullscreen/browser.toml54
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-api-keys.js218
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js127
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js128
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js141
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js122
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-document-mutation.js118
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js128
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-navigation-history.js100
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-navigation-race.js162
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-navigation.js142
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-newtab.js91
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-sizemode.js225
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js105
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-tab-close.js65
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen-window-open-race.js73
-rw-r--r--dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js215
-rw-r--r--dom/base/test/fullscreen/chrome.toml11
-rw-r--r--dom/base/test/fullscreen/dummy_page.html10
-rw-r--r--dom/base/test/fullscreen/file_MozDomFullscreen.html8
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-api-keys.html41
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-api-race.html8
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-api.html340
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-async.html50
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-backdrop.html107
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html22
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-bug-1798219.html14
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-denied-inner.html24
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-denied.html171
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html58
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-esc-exit.html63
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-event-order.html50
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html34
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-featurePolicy.html90
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-focus-inner.html24
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-focus.html67
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-hidden.html56
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-iframe-inner.html5
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-iframe-middle.html5
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-iframe-top.html5
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-lenient-setters.html61
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-multiple-inner.html25
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-multiple.html67
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-navigation.html52
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-nested.html130
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-newtab.html4
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-prefixed.html153
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-resize.html39
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-rollback.html140
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-scrollbar.html147
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-selector.html187
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-shadowdom.html52
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-single.html78
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-sub-iframe.html53
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-svg-element.html49
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-table.html52
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-top-layer.html160
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-utils.js87
-rw-r--r--dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html36
-rw-r--r--dom/base/test/fullscreen/file_fullscreen_meta_viewport.html12
-rw-r--r--dom/base/test/fullscreen/fullscreen.xhtml27
-rw-r--r--dom/base/test/fullscreen/fullscreen_helpers.js174
-rw-r--r--dom/base/test/fullscreen/head.js65
-rw-r--r--dom/base/test/fullscreen/mochitest.toml64
-rw-r--r--dom/base/test/fullscreen/moz.build17
-rw-r--r--dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml46
-rw-r--r--dom/base/test/fullscreen/test_fullscreen-api-race.html177
-rw-r--r--dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html167
-rw-r--r--dom/base/test/fullscreen/test_fullscreen-api.html150
-rw-r--r--dom/base/test/fullscreen/test_fullscreen.xhtml37
-rw-r--r--dom/base/test/fullscreen/test_fullscreen_meta_viewport.html33
-rw-r--r--dom/base/test/fullscreen/test_fullscreen_modal.html69
73 files changed, 6195 insertions, 0 deletions
diff --git a/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml b/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml
new file mode 100644
index 0000000000..93f00311e7
--- /dev/null
+++ b/dom/base/test/fullscreen/MozDomFullscreen_chrome.xhtml
@@ -0,0 +1,108 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test that "MozDOMFullscreen:*" events are dispatched to chrome on documents that use DOM fullscreen.
+
+ Test Description:
+
+ This chrome window has a browser. The browser's contentDocument (the "outer document")
+ in turn has an iframe (the "inner document").
+
+ We request fullscreen in the outer document, and check that MozDOMFullscreen:Entered and
+ MozDOMFullscreen:NewOrigin are dispatched to chrome, targeted at the outer document.
+
+ Then we request fullscreen in the inner document, and check that MozDOMFullscreen:NewOrigin
+ is dispatched to chrome, targeted at the inner document.
+
+ Then we cancel fullscreen in the inner document, and check that MozDOMFullscreen:NewOrigin is
+ dispatched again to chrome, targeted at the outer document.
+-->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="start();">
+
+<script src="chrome://mochikit/content/chrome-harness.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script type="application/javascript"><![CDATA[
+
+function ok(condition, msg) {
+ window.arguments[0].ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ window.arguments[0].is(a, b, msg);
+}
+
+var gBrowser = null;
+var gOuterDoc = null;
+var gInnerDoc = null;
+
+var gReceivedFullscreenEnteredEvent = false;
+function firstEntry(event) {
+ if (event.type == "MozDOMFullscreen:NewOrigin") {
+ ok(false, "MozDOMFullscreen:NewOrigin shouldn't be triggered at first entry");
+ return;
+ }
+
+ if (event.type != "MozDOMFullscreen:Entered") {
+ ok(false, "Unknown event received");
+ return;
+ }
+
+ ok(gOuterDoc.fullscreenElement != null, "Outer doc should be in fullscreen");
+ is(event.target, gOuterDoc.body, "First MozDOMFullscreen:Entered should be targeted at outer body");
+ ok(!gReceivedFullscreenEnteredEvent, "MozDOMFullscreen:Entered shouldn't have been triggered twice");
+ gReceivedFullscreenEnteredEvent = true;
+ window.removeEventListener("MozDOMFullscreen:Entered", firstEntry);
+ window.removeEventListener("MozDOMFullscreen:NewOrigin", firstEntry);
+
+ window.addEventListener("MozDOMFullscreen:NewOrigin", secondEntry);
+ gInnerDoc = gOuterDoc.getElementById("innerFrame").contentDocument;
+ gInnerDoc.defaultView.focus();
+ gInnerDoc.body.requestFullscreen();
+}
+
+function secondEntry(event) {
+ is(event.target, gInnerDoc, "Second MozDOMFullscreen:NewOrigin should be targeted at inner doc");
+ ok(gInnerDoc.fullscreenElement != null, "Inner doc should be in fullscreen");
+ window.removeEventListener("MozDOMFullscreen:NewOrigin", secondEntry);
+ window.addEventListener("MozDOMFullscreen:NewOrigin", thirdEntry);
+ gInnerDoc.exitFullscreen();
+}
+
+function thirdEntry(event) {
+ is(event.target, gOuterDoc, "Third MozDOMFullscreen:NewOrigin should be targeted at outer doc");
+ ok(gOuterDoc.fullscreenElement != null, "Outer doc return to fullscreen after cancel fullscreen in inner doc");
+ window.removeEventListener("MozDOMFullscreen:NewOrigin", thirdEntry);
+ window.removeEventListener("MozDOMFullscreen:Exited", earlyExit);
+ window.addEventListener("MozDOMFullscreen:Exited", lastExit);
+ gOuterDoc.exitFullscreen();
+}
+
+function earlyExit(event) {
+ ok(false, "MozDOMFullscreen:Exited should only be triggered after cancel all fullscreen");
+}
+
+function lastExit(event) {
+ is(event.target, gOuterDoc, "MozDOMFullscreen:Exited should be targeted at the last exited doc");
+ ok(gOuterDoc.fullscreenElement == null, "Fullscreen should have been fully exited");
+ window.arguments[0].done();
+}
+
+function start() {
+ SimpleTest.waitForFocus(
+ function() {
+ gBrowser = document.getElementById("browser");
+ gOuterDoc = gBrowser.contentDocument;
+ gBrowser.contentWindow.focus();
+ window.addEventListener("MozDOMFullscreen:Entered", firstEntry);
+ window.addEventListener("MozDOMFullscreen:NewOrigin", firstEntry);
+ gOuterDoc.body.requestFullscreen();
+ });
+}
+
+]]>
+</script>
+<browser type="content" id="browser" width="400" height="400" src="file_MozDomFullscreen.html"/>
+
+</window>
diff --git a/dom/base/test/fullscreen/browser.toml b/dom/base/test/fullscreen/browser.toml
new file mode 100644
index 0000000000..dc883a4ac2
--- /dev/null
+++ b/dom/base/test/fullscreen/browser.toml
@@ -0,0 +1,54 @@
+[DEFAULT]
+tags = "fullscreen"
+head = "head.js"
+support-files = [
+ "dummy_page.html",
+ "file_fullscreen-api-keys.html",
+ "file_fullscreen-iframe-inner.html",
+ "file_fullscreen-iframe-middle.html",
+ "file_fullscreen-iframe-top.html",
+ "file_fullscreen-newtab.html",
+ "fullscreen_helpers.js",
+]
+
+["browser_fullscreen-api-keys.js"]
+
+["browser_fullscreen-bug-1798219.js"]
+skip-if = ["!nightly_build"] # Bug 1818608
+support-files = [
+ "file_fullscreen-bug-1798219.html",
+ "file_fullscreen-bug-1798219-2.html",
+]
+
+["browser_fullscreen-contextmenu-esc.js"]
+
+["browser_fullscreen-document-mutation-navigation.js"]
+
+["browser_fullscreen-document-mutation-race.js"]
+
+["browser_fullscreen-document-mutation.js"]
+
+["browser_fullscreen-navigation-history-race.js"]
+
+["browser_fullscreen-navigation-history.js"]
+
+["browser_fullscreen-navigation-race.js"]
+
+["browser_fullscreen-navigation.js"]
+
+["browser_fullscreen-newtab.js"]
+skip-if = [
+ "os == 'mac'", # Bug 1494843
+ "os == 'linux' && bits == 64 && os_version == '18.04'", # Bug 1601460
+]
+
+["browser_fullscreen-sizemode.js"]
+
+["browser_fullscreen-tab-close-race.js"]
+
+["browser_fullscreen-tab-close.js"]
+
+["browser_fullscreen-window-open-race.js"]
+skip-if = ["os == 'mac'"] # test is checking for synchronous fullscreen completion
+
+["browser_fullscreen_exit_on_external_protocol.js"]
diff --git a/dom/base/test/fullscreen/browser_fullscreen-api-keys.js b/dom/base/test/fullscreen/browser_fullscreen-api-keys.js
new file mode 100644
index 0000000000..1b1a07975e
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-api-keys.js
@@ -0,0 +1,218 @@
+"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);
+
+/** Test for Bug 545812 **/
+
+// List of key codes which should exit full-screen mode.
+const kKeyList = [
+ { key: "Escape", keyCode: "VK_ESCAPE", suppressed: true },
+ { key: "F11", keyCode: "VK_F11", suppressed: false },
+];
+
+function receiveExpectedKeyEvents(aBrowser, aKeyCode, aTrusted) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [aKeyCode, aTrusted],
+ (keyCode, trusted) => {
+ return new Promise(resolve => {
+ let events = trusted
+ ? ["keydown", "keyup"]
+ : ["keydown", "keypress", "keyup"];
+ if (trusted && keyCode == content.wrappedJSObject.KeyEvent.DOM_VK_F11) {
+ // trusted `F11` key shouldn't be fired because of reserved when it's
+ // a shortcut key for exiting from the full screen mode.
+ events.shift();
+ }
+ function listener(event) {
+ let expected = events.shift();
+ Assert.equal(
+ event.type,
+ expected,
+ `Should receive a ${expected} event`
+ );
+ Assert.equal(
+ event.keyCode,
+ keyCode,
+ `Should receive the event with key code ${keyCode}`
+ );
+ if (!events.length) {
+ content.document.removeEventListener("keydown", listener, true);
+ content.document.removeEventListener("keyup", listener, true);
+ content.document.removeEventListener("keypress", listener, true);
+ resolve();
+ }
+ }
+
+ content.document.addEventListener("keydown", listener, true);
+ content.document.addEventListener("keyup", listener, true);
+ content.document.addEventListener("keypress", listener, true);
+ });
+ }
+ );
+}
+
+const kPage =
+ "https://example.org/browser/" +
+ "dom/base/test/fullscreen/file_fullscreen-api-keys.html";
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ );
+
+ let tab = BrowserTestUtils.addTab(gBrowser, kPage);
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ registerCleanupFunction(() => gBrowser.removeTab(tab));
+ await waitForDocLoadComplete();
+
+ // Wait for the document being activated, so that
+ // fullscreen request won't be denied.
+ await SpecialPowers.spawn(browser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.browsingContext.isActive && content.document.hasFocus(),
+ "document is active"
+ );
+ });
+
+ // Register listener to capture unexpected events
+ let keyEventsCount = 0;
+ let fullScreenEventsCount = 0;
+ let removeFullScreenListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ () => fullScreenEventsCount++
+ );
+ let removeKeyDownListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "keydown",
+ () => keyEventsCount++,
+ { wantUntrusted: true }
+ );
+ let removeKeyPressListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "keypress",
+ () => keyEventsCount++,
+ { wantUntrusted: true }
+ );
+ let removeKeyUpListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "keyup",
+ () => keyEventsCount++,
+ { wantUntrusted: true }
+ );
+
+ let expectedFullScreenEventsCount = 0;
+ let expectedKeyEventsCount = 0;
+
+ for (let { key, keyCode, suppressed } of kKeyList) {
+ let keyCodeValue = KeyEvent["DOM_" + keyCode];
+ info(`Test keycode ${key} (${keyCodeValue})`);
+
+ info("Enter fullscreen");
+ let state = new Promise(resolve => {
+ let removeFun = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ async () => {
+ removeFun();
+ resolve(
+ await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ })
+ );
+ }
+ );
+ });
+ // request fullscreen
+ SpecialPowers.spawn(browser, [], () => {
+ content.document.body.requestFullscreen();
+ });
+ ok(await state, "The content should have entered fullscreen");
+ ok(document.fullscreenElement, "The chrome should also be in fullscreen");
+
+ is(
+ fullScreenEventsCount,
+ ++expectedFullScreenEventsCount,
+ "correct number of fullscreen events occurred"
+ );
+
+ info("Dispatch untrusted key events from content");
+ let promiseExpectedKeyEvents = receiveExpectedKeyEvents(
+ browser,
+ keyCodeValue,
+ false
+ );
+
+ SpecialPowers.spawn(browser, [keyCode], keyCodeChild => {
+ var evt = new content.CustomEvent("Test:DispatchKeyEvents", {
+ detail: Cu.cloneInto({ code: keyCodeChild }, content),
+ });
+ content.dispatchEvent(evt);
+ });
+ await promiseExpectedKeyEvents;
+
+ expectedKeyEventsCount += 3;
+ is(
+ keyEventsCount,
+ expectedKeyEventsCount,
+ "correct number of key events occurred"
+ );
+
+ info("Send trusted key events");
+
+ state = new Promise(resolve => {
+ let removeFun = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ async () => {
+ removeFun();
+ resolve(
+ await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ })
+ );
+ }
+ );
+ });
+
+ promiseExpectedKeyEvents = suppressed
+ ? Promise.resolve()
+ : receiveExpectedKeyEvents(browser, keyCodeValue, true);
+ await SpecialPowers.spawn(browser, [], () => {});
+
+ EventUtils.synthesizeKey("KEY_" + key);
+ await promiseExpectedKeyEvents;
+
+ ok(!(await state), "The content should have exited fullscreen");
+ ok(
+ !document.fullscreenElement,
+ "The chrome should also have exited fullscreen"
+ );
+
+ is(
+ fullScreenEventsCount,
+ ++expectedFullScreenEventsCount,
+ "correct number of fullscreen events occurred"
+ );
+ if (!suppressed) {
+ expectedKeyEventsCount += keyCode == "VK_F11" ? 1 : 3;
+ }
+ is(
+ keyEventsCount,
+ expectedKeyEventsCount,
+ "correct number of key events occurred"
+ );
+ }
+
+ removeFullScreenListener();
+ removeKeyDownListener();
+ removeKeyPressListener();
+ removeKeyUpListener();
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js b/dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js
new file mode 100644
index 0000000000..2aef23b042
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-bug-1798219.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Import helpers
+/* import-globals-from fullscreen_helpers.js */
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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, https://bugzilla.mozilla.org/show_bug.cgi?id=1742890.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function waitAndCheckFullscreenState(aWindow) {
+ // Wait fullscreen exit event if browser is still in fullscreen mode.
+ if (
+ aWindow.fullScreen ||
+ aWindow.document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ info("The widget is still in fullscreen, wait again");
+ await waitWidgetFullscreenEvent(aWindow, false, true);
+ }
+ if (aWindow.document.documentElement.hasAttribute("inDOMFullscreen")) {
+ info("The chrome document is still in fullscreen, wait again");
+ await waitForFullScreenObserver(aWindow, false, true);
+ }
+
+ // Ensure the browser exits fullscreen state.
+ ok(!aWindow.fullScreen, "The widget should not be in fullscreen");
+ ok(
+ !aWindow.document.documentElement.hasAttribute("inFullscreen"),
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !aWindow.document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+}
+
+add_task(async () => {
+ const URL =
+ "http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html";
+ // We need this dummy tab which load the same URL as test tab to keep the
+ // original content process alive after test page navigates away.
+ let dummyTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: URL,
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.querySelector("button").click();
+ });
+
+ // Test requests fullscreen and performs navigation simultaneously,
+ // the fullscreen request might be rejected directly if navigation happens
+ // first, so there might be no reliable state that we can wait. So give
+ // some time for possible fullscreen transition instead and ensure window
+ // should end up exiting fullscreen.
+ await new Promise(aResolve => {
+ SimpleTest.executeSoon(() => {
+ SimpleTest.executeSoon(aResolve);
+ });
+ });
+ await waitAndCheckFullscreenState(window);
+ }
+ );
+
+ let dummyTabClosed = BrowserTestUtils.waitForTabClosing(dummyTab);
+ BrowserTestUtils.removeTab(dummyTab);
+ await dummyTabClosed;
+});
+
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html",
+ },
+ async function (browser) {
+ // Open a new window to run the tests, the original window will keep the
+ // original content process alive after the test window navigates away.
+ let promiseWin = BrowserTestUtils.waitForNewWindow();
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.querySelector("button").click();
+ });
+ let newWindow = await promiseWin;
+
+ await SpecialPowers.spawn(
+ newWindow.gBrowser.selectedTab.linkedBrowser,
+ [],
+ function () {
+ content.document.querySelector("button").click();
+ }
+ );
+
+ // Test requests fullscreen and performs navigation simultaneously,
+ // the fullscreen request might be rejected directly if navigation happens
+ // first, so there might be no reliable state that we can wait. So give
+ // some time for possible fullscreen transition instead and ensure window
+ // should end up exiting fullscreen.
+ await new Promise(aResolve => {
+ SimpleTest.executeSoon(() => {
+ SimpleTest.executeSoon(aResolve);
+ });
+ });
+ await waitAndCheckFullscreenState(newWindow);
+
+ newWindow.close();
+ }
+ );
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js b/dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js
new file mode 100644
index 0000000000..e89409a90f
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-contextmenu-esc.js
@@ -0,0 +1,128 @@
+"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);
+
+function captureUnexpectedFullscreenChange() {
+ ok(false, "Caught an unexpected fullscreen change");
+}
+
+const kPage =
+ "https://example.org/browser/dom/base/test/fullscreen/dummy_page.html";
+
+function waitForDocActivated(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.browsingContext.isActive && content.document.hasFocus()
+ );
+ });
+}
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: kPage,
+ waitForStateStop: true,
+ });
+ let browser = tab.linkedBrowser;
+
+ // As requestFullscreen checks the active state of the docshell,
+ // wait for the document to be activated, just to be sure that
+ // the fullscreen request won't be denied.
+ await SpecialPowers.spawn(browser, [], () => {
+ return ContentTaskUtils.waitForCondition(
+ () => content.browsingContext.isActive && content.document.hasFocus()
+ );
+ });
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ ok(contextMenu, "Got context menu");
+
+ let state;
+ info("Enter DOM fullscreen");
+ let fullScreenChangedPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "fullscreenchange"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.requestFullscreen();
+ });
+
+ await fullScreenChangedPromise;
+ state = await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ });
+ ok(state, "The content should have entered fullscreen");
+ ok(document.fullscreenElement, "The chrome should also be in fullscreen");
+
+ let removeContentEventListener = BrowserTestUtils.addContentEventListener(
+ browser,
+ "fullscreenchange",
+ captureUnexpectedFullscreenChange
+ );
+
+ info("Open context menu");
+ is(contextMenu.state, "closed", "Should not have opened context menu");
+
+ let popupShownPromise = promiseWaitForEvent(window, "popupshown");
+
+ EventUtils.synthesizeMouse(
+ browser,
+ screen.width / 2,
+ screen.height / 2,
+ { type: "contextmenu", button: 2 },
+ window
+ );
+ await popupShownPromise;
+ is(contextMenu.state, "open", "Should have opened context menu");
+
+ let popupHidePromise = promiseWaitForEvent(window, "popuphidden");
+
+ if (
+ !AppConstants.platform == "macosx" ||
+ !Services.prefs.getBoolPref("widget.macos.native-context-menus", false)
+ ) {
+ info("Send the first escape");
+ EventUtils.synthesizeKey("KEY_Escape");
+ } else {
+ // We cannot synthesize key events at native macOS menus.
+ // We also do not see key events that are processed by native macOS menus,
+ // so we cannot accidentally exit fullscreen when the user closes a native
+ // menu with Escape.
+ // Close the menu normally.
+ info("Close the context menu");
+ contextMenu.hidePopup();
+ }
+ await popupHidePromise;
+ is(contextMenu.state, "closed", "Should have closed context menu");
+
+ // Wait a small time to confirm that the first ESC key
+ // does not exit fullscreen.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ state = await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.fullscreenElement;
+ });
+ ok(state, "The content should still be in fullscreen");
+ ok(document.fullscreenElement, "The chrome should still be in fullscreen");
+
+ removeContentEventListener();
+ info("Send the second escape");
+ let fullscreenExitPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "fullscreenchange"
+ );
+ EventUtils.synthesizeKey("KEY_Escape");
+ await fullscreenExitPromise;
+ ok(!document.fullscreenElement, "The chrome should have exited fullscreen");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js
new file mode 100644
index 0000000000..f6b5715f59
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-navigation.js
@@ -0,0 +1,141 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(testFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // This should exit fullscreen
+ promiseFsState = waitForFullscreenState(document, false, true);
+ await testFun(browser);
+ await promiseFsState;
+
+ // This test triggers a fullscreen request during the fullscreen exit
+ // process, so it could be possible that the widget or the chrome
+ // document goes into fullscreen mode again, but they should end up
+ // leaving fullscreen mode again.
+ if (
+ window.fullScreen ||
+ document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ info("widget is still in fullscreen, wait again");
+ await waitWidgetFullscreenEvent(window, false, true);
+ }
+ if (document.documentElement.hasAttribute("inDOMFullscreen")) {
+ info("chrome document is still in fullscreen, wait again");
+ await waitForFullScreenObserver(document, false, true);
+ }
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The widget should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inFullscreen"),
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function MutateAndNavigateFromRemoteDocument(
+ aBrowsingContext,
+ aElementId,
+ aURL
+) {
+ return SpecialPowers.spawn(
+ aBrowsingContext,
+ [aElementId, aURL],
+ async function (id, url) {
+ let element = content.document.getElementById(id);
+ element.requestFullscreen();
+ content.document.body.appendChild(element);
+ content.location.href = url;
+ }
+ );
+}
+
+startTests(async browser => {
+ // toplevel
+ await MutateAndNavigateFromRemoteDocument(
+ browser.browsingContext,
+ "div",
+ "about:blank"
+ );
+}, "document_mutation_navigation_toplevel");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ // middle iframe
+ await MutateAndNavigateFromRemoteDocument(
+ browser.browsingContext.children[0],
+ "div",
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_navigation_middle_frame");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ // innermost iframe
+ await MutateAndNavigateFromRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "div",
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_navigation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js
new file mode 100644
index 0000000000..75ca199aaa
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-document-mutation-race.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(setupFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = Promise.all([
+ setupFun(browser),
+ waitForFullscreenState(document, false, true),
+ ]);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function RemoveElementFromRemoteDocument(aBrowsingContext, aElementId) {
+ return SpecialPowers.spawn(
+ aBrowsingContext,
+ [aElementId],
+ async function (id) {
+ content.document.addEventListener(
+ "fullscreenchange",
+ function () {
+ content.document.getElementById(id).remove();
+ },
+ { once: true }
+ );
+ }
+ );
+}
+
+startTests(async browser => {
+ // toplevel
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ await RemoveElementFromRemoteDocument(browser.browsingContext, "div");
+ return promise;
+}, "document_mutation_toplevel");
+
+startTests(async browser => {
+ // middle iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0],
+ "div"
+ );
+ return promise;
+}, "document_mutation_middle_frame");
+
+startTests(async browser => {
+ // innermost iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ [browser.browsingContext.children[0].children[0], "inner"],
+ ]);
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "div"
+ );
+ return promise;
+}, "document_mutation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-document-mutation.js b/dom/base/test/fullscreen/browser_fullscreen-document-mutation.js
new file mode 100644
index 0000000000..7cecdabb95
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-document-mutation.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(testFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // This should exit fullscreen
+ promiseFsState = waitForFullscreenState(document, false);
+ await testFun(browser);
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function RemoveElementFromRemoteDocument(aBrowsingContext, aElementId) {
+ return SpecialPowers.spawn(
+ aBrowsingContext,
+ [aElementId],
+ async function (id) {
+ content.document.getElementById(id).remove();
+ }
+ );
+}
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ // toplevel
+ await RemoveElementFromRemoteDocument(browser.browsingContext, "div");
+ await promiseRemoteFsState;
+}, "document_mutation_toplevel");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ // middle iframe
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0],
+ "div"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_middle_frame");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ [browser.browsingContext.children[0].children[0], "inner"],
+ ]);
+ // innermost iframe
+ await RemoveElementFromRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "div"
+ );
+ await promiseRemoteFsState;
+}, "document_mutation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js b/dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js
new file mode 100644
index 0000000000..2ea2b9ee40
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-navigation-history-race.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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, bug 1742890.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+function preventBFCache(aBrowsingContext, aPrevent) {
+ return SpecialPowers.spawn(aBrowsingContext, [aPrevent], prevent => {
+ if (prevent) {
+ // Using a dummy onunload listener to disable the bfcache.
+ content.window.addEventListener("unload", () => {});
+ }
+ content.window.addEventListener(
+ "pagehide",
+ e => {
+ // XXX checking persisted property causes intermittent failures, so we
+ // dump the value instead, bug 1822263.
+ // is(e.persisted, !prevent, `Check BFCache state`);
+ info(`Check BFCache state: e.persisted is ${e.persisted}`);
+ },
+ { once: true }
+ );
+ });
+}
+
+[true, false].forEach(crossOrigin => {
+ [true, false].forEach(initialPagePreventsBFCache => {
+ [true, false].forEach(fullscreenPagePreventsBFCache => {
+ add_task(async function navigation_history() {
+ info(
+ `crossOrigin: ${crossOrigin}, initialPagePreventsBFCache: ${initialPagePreventsBFCache}, fullscreenPagePreventsBFCache: ${fullscreenPagePreventsBFCache}`
+ );
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://mochi.test:8888/browser/dom/base/test/fullscreen/dummy_page.html",
+ },
+ async function (browser) {
+ // Maybe prevent BFCache on initial page.
+ await preventBFCache(
+ browser.browsingContext,
+ initialPagePreventsBFCache
+ );
+
+ // Navigate to fullscreen page.
+ const url = crossOrigin
+ ? "https://example.org/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html"
+ : "http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html";
+ const loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ BrowserTestUtils.startLoadingURIString(browser, url);
+ await loaded;
+
+ // Maybe prevent BFCache on fullscreen test page.
+ await preventBFCache(
+ browser.browsingContext,
+ fullscreenPagePreventsBFCache
+ );
+
+ // Trigger click event to enter fullscreen.
+ await SpecialPowers.spawn(browser.browsingContext, [], () => {
+ let target = content.document.getElementById("div");
+ target.addEventListener(
+ "mousedown",
+ function (e) {
+ content.window.history.back();
+ },
+ { once: true }
+ );
+ EventUtils.synthesizeMouseAtCenter(target, {}, content.window);
+ });
+
+ // Give some time for fullscreen transition.
+ await new Promise(aResolve => {
+ SimpleTest.executeSoon(() => {
+ SimpleTest.executeSoon(aResolve);
+ });
+ });
+
+ // Wait fullscreen exit event if browser is still in fullscreen mode.
+ if (
+ window.fullScreen ||
+ document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ info("The widget is still in fullscreen, wait again");
+ await waitWidgetFullscreenEvent(window, false, true);
+ }
+ if (document.documentElement.hasAttribute("inDOMFullscreen")) {
+ info("The chrome document is still in fullscreen, wait again");
+ await waitForFullScreenObserver(window, false, true);
+ }
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The widget should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inFullscreen"),
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+ });
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation-history.js b/dom/base/test/fullscreen/browser_fullscreen-navigation-history.js
new file mode 100644
index 0000000000..c4feb7f641
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-navigation-history.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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, bug 1742890.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+function preventBFCache(aBrowsingContext, aPrevent) {
+ return SpecialPowers.spawn(aBrowsingContext, [aPrevent], prevent => {
+ if (prevent) {
+ // Using a dummy onunload listener to disable the bfcache.
+ content.window.addEventListener("unload", () => {});
+ }
+ content.window.addEventListener(
+ "pagehide",
+ e => {
+ // XXX checking persisted property causes intermittent failures, so we
+ // dump the value instead, bug 1822263.
+ // is(e.persisted, !prevent, `Check BFCache state`);
+ info(`Check BFCache state: e.persisted is ${e.persisted}`);
+ },
+ { once: true }
+ );
+ });
+}
+
+[true, false].forEach(crossOrigin => {
+ [true, false].forEach(initialPagePreventsBFCache => {
+ [true, false].forEach(fullscreenPagePreventsBFCache => {
+ add_task(async function navigation_history() {
+ info(
+ `crossOrigin: ${crossOrigin}, initialPagePreventsBFCache: ${initialPagePreventsBFCache}, fullscreenPagePreventsBFCache: ${fullscreenPagePreventsBFCache}`
+ );
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://mochi.test:8888/browser/dom/base/test/fullscreen/dummy_page.html",
+ },
+ async function (browser) {
+ // Maybe prevent BFCache on initial page.
+ await preventBFCache(
+ browser.browsingContext,
+ initialPagePreventsBFCache
+ );
+
+ // Navigate to fullscreen page.
+ const url = crossOrigin
+ ? "https://example.org/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html"
+ : "http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html";
+ const loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ BrowserTestUtils.startLoadingURIString(browser, url);
+ await loaded;
+
+ // Maybe prevent BFCache on fullscreen test page.
+ await preventBFCache(
+ browser.browsingContext,
+ fullscreenPagePreventsBFCache
+ );
+
+ // Trigger click event to enter fullscreen.
+ let promiseFsState = waitForFullscreenState(document, true);
+ SpecialPowers.spawn(browser.browsingContext, [], () => {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ });
+ await promiseFsState;
+
+ // Navigate back to the previous page should exit fullscreen.
+ promiseFsState = waitForFullscreenState(document, false);
+ await SpecialPowers.spawn(browser.browsingContext, [], () => {
+ content.window.history.back();
+ });
+ await promiseFsState;
+ }
+ );
+ });
+ });
+ });
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation-race.js b/dom/base/test/fullscreen/browser_fullscreen-navigation-race.js
new file mode 100644
index 0000000000..f9d1543a1a
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-navigation-race.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+add_task(async function navigation() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <button id="button">Click here</button>
+ <script>
+ let button = document.getElementById("button");
+ button.addEventListener("click", function() {
+ button.requestFullscreen();
+ location.href = "about:blank";
+ });
+ </script>`,
+ },
+ async function (browser) {
+ BrowserTestUtils.synthesizeMouseAtCenter("#button", {}, browser);
+
+ // Give some time for fullscreen transition.
+ await new Promise(aResolve => {
+ SimpleTest.executeSoon(() => {
+ SimpleTest.executeSoon(aResolve);
+ });
+ });
+
+ // Wait fullscreen exit event if browser is still in fullscreen mode.
+ if (
+ window.fullScreen ||
+ document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ info("The widget is still in fullscreen, wait again");
+ await waitWidgetFullscreenEvent(window, false, true);
+ }
+ if (document.documentElement.hasAttribute("inDOMFullscreen")) {
+ info("The chrome document is still in fullscreen, wait again");
+ await waitForFullScreenObserver(window, false, true);
+ }
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The widget should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inFullscreen"),
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+});
+
+async function startTests(setupFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = Promise.all([
+ setupFun(browser),
+ waitForFullscreenState(document, false, true),
+ ]);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function NavigateRemoteDocument(aBrowsingContext, aURL) {
+ return SpecialPowers.spawn(aBrowsingContext, [aURL], async function (url) {
+ content.document.addEventListener(
+ "fullscreenchange",
+ function () {
+ content.location.href = url;
+ },
+ { once: true }
+ );
+ });
+}
+
+startTests(async browser => {
+ // toplevel
+ await NavigateRemoteDocument(browser.browsingContext, "about:blank");
+}, "navigation_toplevel");
+
+startTests(async browser => {
+ // middle iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0],
+ "about:blank"
+ );
+ return promise;
+}, "navigation_middle_frame");
+
+startTests(async browser => {
+ // innermost iframe
+ let promise = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "about:blank"
+ );
+ return promise;
+}, "navigation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-navigation.js b/dom/base/test/fullscreen/browser_fullscreen-navigation.js
new file mode 100644
index 0000000000..02387eb437
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-navigation.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+add_task(async function navigation() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: `data:text/html,
+ <button id="button">Click here</button>
+ <script>
+ let button = document.getElementById("button");
+ button.addEventListener("click", function() {
+ button.requestFullscreen();
+ });
+ </script>`,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event
+ BrowserTestUtils.synthesizeMouseAtCenter("#button", {}, browser);
+ await promiseFsState;
+
+ promiseFsState = waitForFullscreenState(document, false);
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.location.href = "about:blank";
+ });
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+});
+
+async function startTests(testFun, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ // This should exit fullscreen
+ promiseFsState = waitForFullscreenState(document, false);
+ await testFun(browser);
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+function NavigateRemoteDocument(aBrowsingContext, aURL) {
+ return SpecialPowers.spawn(aBrowsingContext, [aURL], async function (url) {
+ content.location.href = url;
+ });
+}
+
+startTests(async browser => {
+ // toplevel
+ await NavigateRemoteDocument(browser.browsingContext, "about:blank");
+}, "navigation_toplevel");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ ]);
+ // middle iframe
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0],
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "navigation_middle_frame");
+
+startTests(async browser => {
+ let promiseRemoteFsState = waitRemoteFullscreenExitEvents([
+ // browsingContext, name
+ [browser.browsingContext, "toplevel"],
+ [browser.browsingContext.children[0], "middle"],
+ ]);
+ // innermost iframe
+ await NavigateRemoteDocument(
+ browser.browsingContext.children[0].children[0],
+ "about:blank"
+ );
+ await promiseRemoteFsState;
+}, "navigation_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-newtab.js b/dom/base/test/fullscreen/browser_fullscreen-newtab.js
new file mode 100644
index 0000000000..af714b1248
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-newtab.js
@@ -0,0 +1,91 @@
+"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 kPage =
+ "https://example.org/browser/" +
+ "dom/base/test/fullscreen/file_fullscreen-newtab.html";
+
+function getSizeMode() {
+ return document.documentElement.getAttribute("sizemode");
+}
+
+async function runTest() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: kPage,
+ },
+ async function (browser) {
+ let promiseFsEvents = SpecialPowers.spawn(browser, [], function () {
+ return new Promise(resolve => {
+ let countFsChange = 0;
+ let countFsError = 0;
+ function checkAndResolve() {
+ if (countFsChange > 0 && countFsError > 0) {
+ Assert.ok(
+ false,
+ "Got both fullscreenchange and fullscreenerror events"
+ );
+ } else if (countFsChange > 2) {
+ Assert.ok(false, "Got too many fullscreenchange events");
+ } else if (countFsError > 1) {
+ Assert.ok(false, "Got too many fullscreenerror events");
+ } else if (countFsChange == 2 || countFsError == 1) {
+ resolve();
+ }
+ }
+
+ content.document.addEventListener("fullscreenchange", () => {
+ ++countFsChange;
+ checkAndResolve();
+ });
+ content.document.addEventListener("fullscreenerror", () => {
+ ++countFsError;
+ checkAndResolve();
+ });
+ });
+ });
+ let promiseNewTab = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:blank"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter("#link", {}, browser);
+ let [newtab] = await Promise.all([promiseNewTab, promiseFsEvents]);
+ await BrowserTestUtils.removeTab(newtab);
+
+ // Ensure the browser exits fullscreen state in reasonable time.
+ await Promise.race([
+ BrowserTestUtils.waitForCondition(() => getSizeMode() == "normal"),
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ new Promise(resolve => setTimeout(resolve, 2000)),
+ ]);
+
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.fullscreen,
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+}
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]
+ );
+ await runTest();
+});
+
+add_task(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "200 200"],
+ ["full-screen-api.transition-duration.leave", "200 200"]
+ );
+ await runTest();
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-sizemode.js b/dom/base/test/fullscreen/browser_fullscreen-sizemode.js
new file mode 100644
index 0000000000..0aa79e5694
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-sizemode.js
@@ -0,0 +1,225 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const isMac = AppConstants.platform == "macosx";
+const isWin = AppConstants.platform == "win";
+
+async function waitForSizeMode(aWindow, aSizeMode) {
+ await BrowserTestUtils.waitForEvent(aWindow, "sizemodechange", false, () => {
+ return aWindow.windowState === aSizeMode;
+ });
+ const expectedHidden =
+ aSizeMode == aWindow.STATE_MINIMIZED || aWindow.isFullyOccluded;
+ if (aWindow.document.hidden != expectedHidden) {
+ await BrowserTestUtils.waitForEvent(aWindow, "visibilitychange");
+ }
+ is(
+ aWindow.document.hidden,
+ expectedHidden,
+ "Should be inactive if minimized or occluded"
+ );
+}
+
+async function checkSizeModeAndFullscreenState(
+ aWindow,
+ aSizeMode,
+ aFullscreen,
+ aFullscreenEventShouldHaveFired,
+ aStepFun
+) {
+ let promises = [];
+ if (aWindow.windowState != aSizeMode) {
+ promises.push(waitForSizeMode(aWindow, aSizeMode));
+ }
+ if (aFullscreenEventShouldHaveFired) {
+ promises.push(
+ BrowserTestUtils.waitForEvent(
+ aWindow,
+ aFullscreen ? "willenterfullscreen" : "willexitfullscreen"
+ )
+ );
+ promises.push(BrowserTestUtils.waitForEvent(aWindow, "fullscreen"));
+ }
+
+ // Add listener for unexpected event.
+ let unexpectedEventListener = aEvent => {
+ ok(false, `should not receive ${aEvent.type} event`);
+ };
+ if (aFullscreenEventShouldHaveFired) {
+ aWindow.addEventListener(
+ aFullscreen ? "willexitfullscreen" : "willenterfullscreen",
+ unexpectedEventListener
+ );
+ } else {
+ aWindow.addEventListener("willenterfullscreen", unexpectedEventListener);
+ aWindow.addEventListener("willexitfullscreen", unexpectedEventListener);
+ aWindow.addEventListener("fullscreen", unexpectedEventListener);
+ }
+
+ let eventPromise = Promise.all(promises);
+ aStepFun();
+ await eventPromise;
+
+ // Check SizeMode.
+ is(
+ aWindow.windowState,
+ aSizeMode,
+ "The new sizemode should have the expected value"
+ );
+ // Check Fullscreen state.
+ is(
+ aWindow.fullScreen,
+ aFullscreen,
+ `chrome window should ${aFullscreen ? "be" : "not be"} in fullscreen`
+ );
+ is(
+ aWindow.document.documentElement.hasAttribute("inFullscreen"),
+ aFullscreen,
+ `chrome documentElement should ${
+ aFullscreen ? "have" : "not have"
+ } inFullscreen attribute`
+ );
+
+ // Remove listener for unexpected event.
+ if (aFullscreenEventShouldHaveFired) {
+ aWindow.removeEventListener(
+ aFullscreen ? "willexitfullscreen" : "willenterfullscreen",
+ unexpectedEventListener
+ );
+ } else {
+ aWindow.removeEventListener("willenterfullscreen", unexpectedEventListener);
+ aWindow.removeEventListener("willexitfullscreen", unexpectedEventListener);
+ aWindow.removeEventListener("fullscreen", unexpectedEventListener);
+ }
+}
+
+async function restoreWindowToNormal(aWindow) {
+ while (aWindow.windowState != aWindow.STATE_NORMAL) {
+ info(`Try to restore window with state ${aWindow.windowState} to normal`);
+ let eventPromise = BrowserTestUtils.waitForEvent(aWindow, "sizemodechange");
+ aWindow.restore();
+ await eventPromise;
+ info(`Window is now in state ${aWindow.windowState}`);
+ }
+}
+
+add_task(async function test_fullscreen_restore() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await restoreWindowToNormal(win);
+
+ info("Enter fullscreen");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ true,
+ () => {
+ win.fullScreen = true;
+ }
+ );
+
+ info("Restore window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_NORMAL,
+ false,
+ true,
+ () => {
+ win.restore();
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// This test only enable on Windows because:
+// - Test gets intermittent timeout on macOS, see bug 1828848.
+// - Restoring a fullscreen window on GTK doesn't return it to the previous
+// sizemode, see bug 1828837.
+if (isWin) {
+ add_task(async function test_maximize_fullscreen_restore() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await restoreWindowToNormal(win);
+
+ info("Maximize window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_MAXIMIZED,
+ false,
+ false,
+ () => {
+ win.maximize();
+ }
+ );
+
+ info("Enter fullscreen");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ true,
+ () => {
+ win.fullScreen = true;
+ }
+ );
+
+ info("Restore window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_MAXIMIZED,
+ false,
+ true,
+ () => {
+ win.restore();
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ });
+}
+
+// Restoring a minimized window on macOS doesn't return it to the previous
+// sizemode, see bug 1828706.
+if (!isMac) {
+ add_task(async function test_fullscreen_minimize_restore() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await restoreWindowToNormal(win);
+
+ info("Enter fullscreen");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ true,
+ () => {
+ win.fullScreen = true;
+ }
+ );
+
+ info("Minimize window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_MINIMIZED,
+ true,
+ false,
+ () => {
+ win.minimize();
+ }
+ );
+
+ info("Restore window");
+ await checkSizeModeAndFullscreenState(
+ win,
+ win.STATE_FULLSCREEN,
+ true,
+ false,
+ () => {
+ win.restore();
+ }
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ });
+}
diff --git a/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js b/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js
new file mode 100644
index 0000000000..10d10a0b0f
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-tab-close-race.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+async function startTests(setupAndCompletionFn, name) {
+ TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`Test ${name}, url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenExit(document);
+ let promiseSetup = setupAndCompletionFn(browser);
+ // Trigger click event in inner most iframe
+ await SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseSetup;
+ await promiseFsState;
+
+ // Ensure the browser exits fullscreen state.
+ ok(
+ !window.fullScreen,
+ "The chrome window should not be in fullscreen"
+ );
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+ });
+}
+
+async function WaitRemoveDocumentAndCloseTab(aBrowser, aBrowsingContext) {
+ await SpecialPowers.spawn(aBrowsingContext, [], function () {
+ return new Promise(resolve => {
+ content.document.addEventListener(
+ "fullscreenchange",
+ e => {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+ });
+
+ // This should exit fullscreen
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ BrowserTestUtils.removeTab(tab);
+}
+
+startTests(browser => {
+ // toplevel
+ return WaitRemoveDocumentAndCloseTab(browser, browser.browsingContext);
+}, "tab_close_toplevel");
+
+startTests(browser => {
+ // middle iframe
+ return WaitRemoveDocumentAndCloseTab(
+ browser,
+ browser.browsingContext.children[0]
+ );
+}, "tab_close_middle_frame");
+
+startTests(browser => {
+ // innermost iframe
+ return WaitRemoveDocumentAndCloseTab(
+ browser,
+ browser.browsingContext.children[0].children[0]
+ );
+}, "tab_close_inner_frame");
diff --git a/dom/base/test/fullscreen/browser_fullscreen-tab-close.js b/dom/base/test/fullscreen/browser_fullscreen-tab-close.js
new file mode 100644
index 0000000000..7d1772cd48
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-tab-close.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+TEST_URLS.forEach(url => {
+ add_task(async () => {
+ info(`url: ${url}`);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ let promiseFsState = waitForFullscreenState(document, true);
+ // Trigger click event in inner most iframe
+ SpecialPowers.spawn(
+ browser.browsingContext.children[0].children[0],
+ [],
+ function () {
+ content.setTimeout(() => {
+ content.document.getElementById("div").click();
+ }, 0);
+ }
+ );
+ await promiseFsState;
+
+ let promiseFsExit = waitForFullscreenExit(document, false);
+ // This should exit fullscreen
+ let tab = gBrowser.getTabForBrowser(browser);
+ BrowserTestUtils.removeTab(tab);
+ await promiseFsExit;
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+ }
+ );
+ });
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen-window-open-race.js b/dom/base/test/fullscreen/browser_fullscreen-window-open-race.js
new file mode 100644
index 0000000000..4cf8a3d8c7
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen-window-open-race.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+// 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, bug 1742890.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+add_task(async () => {
+ const url =
+ "http://mochi.test:8888/browser/dom/base/test/fullscreen/dummy_page.html";
+ const name = "foo";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ info("open new window");
+ SpecialPowers.spawn(browser, [url, name], function (u, n) {
+ content.document.notifyUserGestureActivation();
+ content.window.open(u, n, "width=100,height=100");
+ });
+ let newWin = await BrowserTestUtils.waitForNewWindow({ url });
+ await SimpleTest.promiseFocus(newWin);
+
+ info("re-focusing main window");
+ await SimpleTest.promiseFocus(window);
+
+ info("open an existing window and request fullscreen");
+ await SpecialPowers.spawn(browser, [url, name], function (u, n) {
+ content.document.notifyUserGestureActivation();
+ content.window.open(u, n);
+ content.document.body.requestFullscreen();
+ });
+
+ // We call window.open() first than requestFullscreen() in a row on
+ // content page, but given that focus sync-up takes several IPC exchanges,
+ // so parent process ends up processing the requests in a reverse order,
+ // which should reject the fullscreen request and leave fullscreen.
+ await waitWidgetFullscreenEvent(window, false, true);
+
+ // Ensure the browser exits fullscreen state.
+ ok(!window.fullScreen, "The chrome window should not be in fullscreen");
+ ok(
+ !document.documentElement.hasAttribute("inDOMFullscreen"),
+ "The chrome document should not be in fullscreen"
+ );
+
+ await BrowserTestUtils.closeWindow(newWin);
+ }
+ );
+});
diff --git a/dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js b/dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js
new file mode 100644
index 0000000000..6f525da541
--- /dev/null
+++ b/dom/base/test/fullscreen/browser_fullscreen_exit_on_external_protocol.js
@@ -0,0 +1,215 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+requestLongerTimeout(2);
+
+// Import helpers
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/dom/base/test/fullscreen/fullscreen_helpers.js",
+ this
+);
+
+add_setup(async function () {
+ await pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ["full-screen-api.allow-trusted-requests-only", false]
+ );
+});
+
+const { HandlerServiceTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/HandlerServiceTestUtils.sys.mjs"
+);
+
+const gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+);
+
+const CONTENT = `data:text/html,
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <button>
+ <a href="mailto:test@example.com"></a>
+ </button>
+ </body>
+ </html>
+`;
+
+// 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);
+
+function setupMailHandler() {
+ let mailHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("mailto");
+ let gOldMailHandlers = [];
+
+ // Remove extant web handlers because they have icons that
+ // we fetch from the web, which isn't allowed in tests.
+ let handlers = mailHandlerInfo.possibleApplicationHandlers;
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ gOldMailHandlers.push(handler);
+ // If we get here, this is a web handler app. Remove it:
+ handlers.removeElementAt(i);
+ } catch (ex) {}
+ }
+
+ let previousHandling = mailHandlerInfo.alwaysAskBeforeHandling;
+ mailHandlerInfo.alwaysAskBeforeHandling = true;
+
+ // Create a dummy web mail handler so we always know the mailto: protocol.
+ // Without this, the test fails on VMs without a default mailto: handler,
+ // because no dialog is ever shown, as we ignore subframe navigations to
+ // protocols that cannot be handled.
+ let dummy = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ dummy.name = "Handler 1";
+ dummy.uriTemplate = "https://example.com/first/%s";
+ mailHandlerInfo.possibleApplicationHandlers.appendElement(dummy);
+
+ gHandlerSvc.store(mailHandlerInfo);
+ registerCleanupFunction(() => {
+ // Re-add the original protocol handlers:
+ let mailHandlers = mailHandlerInfo.possibleApplicationHandlers;
+ for (let i = handlers.Count() - 1; i >= 0; i--) {
+ try {
+ // See if this is a web handler. If it is, it'll throw, otherwise,
+ // we will remove it.
+ mailHandlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ mailHandlers.removeElementAt(i);
+ } catch (ex) {}
+ }
+ for (let h of gOldMailHandlers) {
+ mailHandlers.appendElement(h);
+ }
+ mailHandlerInfo.alwaysAskBeforeHandling = previousHandling;
+ gHandlerSvc.store(mailHandlerInfo);
+ });
+}
+
+add_task(setupMailHandler);
+
+// Fullscreen is canceled during fullscreen transition
+add_task(async function OpenExternalProtocolOnPendingLaterFullscreen() {
+ for (const useClick of [true, false]) {
+ await BrowserTestUtils.withNewTab(CONTENT, async browser => {
+ const leavelFullscreen = waitForFullscreenState(document, false, true);
+ await SpecialPowers.spawn(
+ browser,
+ [useClick],
+ async function (shouldClick) {
+ const button = content.document.querySelector("button");
+
+ const clickDone = new Promise(r => {
+ button.addEventListener(
+ "click",
+ function () {
+ content.document.documentElement.requestFullscreen();
+ // When anchor.click() is called, the fullscreen request
+ // is probably still pending.
+ content.setTimeout(() => {
+ if (shouldClick) {
+ content.document.querySelector("a").click();
+ } else {
+ content.document.location = "mailto:test@example.com";
+ }
+ r();
+ }, 0);
+ },
+ { once: true }
+ );
+ });
+ button.click();
+ await clickDone;
+ }
+ );
+
+ await leavelFullscreen;
+ ok(true, "Fullscreen should be exited");
+ });
+ }
+});
+
+// Fullscreen is canceled immediately.
+add_task(async function OpenExternalProtocolOnPendingFullscreen() {
+ for (const useClick of [true, false]) {
+ await BrowserTestUtils.withNewTab(CONTENT, async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [useClick],
+ async function (shouldClick) {
+ const button = content.document.querySelector("button");
+
+ const clickDone = new Promise(r => {
+ button.addEventListener(
+ "click",
+ function () {
+ content.document.documentElement
+ .requestFullscreen()
+ .then(() => {
+ ok(false, "Don't enter fullscreen");
+ })
+ .catch(() => {
+ ok(true, "Cancel entering fullscreen");
+ r();
+ });
+ // When anchor.click() is called, the fullscreen request
+ // is probably still pending.
+ if (shouldClick) {
+ content.document.querySelector("a").click();
+ } else {
+ content.document.location = "mailto:test@example.com";
+ }
+ },
+ { once: true }
+ );
+ });
+ button.click();
+ await clickDone;
+ }
+ );
+
+ ok(true, "Fullscreen should be exited");
+ });
+ }
+});
+
+add_task(async function OpenExternalProtocolOnFullscreen() {
+ for (const useClick of [true, false]) {
+ await BrowserTestUtils.withNewTab(CONTENT, async browser => {
+ const leavelFullscreen = waitForFullscreenState(document, false, true);
+ await SpecialPowers.spawn(
+ browser,
+ [useClick],
+ async function (shouldClick) {
+ let button = content.document.querySelector("button");
+ button.addEventListener("click", function () {
+ content.document.documentElement.requestFullscreen();
+ });
+ button.click();
+
+ await new Promise(r => {
+ content.document.addEventListener("fullscreenchange", r);
+ });
+
+ if (shouldClick) {
+ content.document.querySelector("a").click();
+ } else {
+ content.document.location = "mailto:test@example.com";
+ }
+ }
+ );
+
+ await leavelFullscreen;
+ ok(true, "Fullscreen should be exited");
+ });
+ }
+});
diff --git a/dom/base/test/fullscreen/chrome.toml b/dom/base/test/fullscreen/chrome.toml
new file mode 100644
index 0000000000..0ba7862f50
--- /dev/null
+++ b/dom/base/test/fullscreen/chrome.toml
@@ -0,0 +1,11 @@
+[DEFAULT]
+tags = "fullscreen"
+
+["test_MozDomFullscreen_event.xhtml"]
+support-files = [
+ "fullscreen.xhtml",
+ "MozDomFullscreen_chrome.xhtml",
+]
+
+["test_fullscreen.xhtml"]
+support-files = "file_MozDomFullscreen.html"
diff --git a/dom/base/test/fullscreen/dummy_page.html b/dom/base/test/fullscreen/dummy_page.html
new file mode 100644
index 0000000000..fd238954c6
--- /dev/null
+++ b/dom/base/test/fullscreen/dummy_page.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Dummy test page</title>
+<meta charset="utf-8"/>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_MozDomFullscreen.html b/dom/base/test/fullscreen/file_MozDomFullscreen.html
new file mode 100644
index 0000000000..f954892706
--- /dev/null
+++ b/dom/base/test/fullscreen/file_MozDomFullscreen.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body style="background-color: blue;">
+<p>Outer doc</p>
+<iframe id="innerFrame" src="http://mochi.test:8888/"></iframe>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-api-keys.html b/dom/base/test/fullscreen/file_fullscreen-api-keys.html
new file mode 100644
index 0000000000..f526aa55ba
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-api-keys.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+<script>
+window.addEventListener("Test:DispatchKeyEvents", aEvent => {
+ var keyCode = KeyEvent["DOM_" + aEvent.detail.code];
+
+ document.body.focus();
+ var evt = new KeyboardEvent("keydown", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ keyCode,
+ charCode: 0,
+ });
+ document.body.dispatchEvent(evt);
+
+ evt = new KeyboardEvent("keypress", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ keyCode,
+ charCode: 0,
+ });
+ document.body.dispatchEvent(evt);
+
+ evt = new KeyboardEvent("keyup", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ keyCode,
+ charCode: 0,
+ });
+ document.body.dispatchEvent(evt);
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-api-race.html b/dom/base/test/fullscreen/file_fullscreen-api-race.html
new file mode 100644
index 0000000000..8310bc0a60
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-api-race.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Helper file for test_fullscreen-api-race.html</title>
+</head>
+<body onload="window.opener.postMessage('ready', '*');">
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-api.html b/dom/base/test/fullscreen/file_fullscreen-api.html
new file mode 100644
index 0000000000..645e6ece46
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-api.html
@@ -0,0 +1,340 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=545812
+
+Test DOM full-screen API.
+
+-->
+<head>
+ <title>Test for Bug 545812</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+<div id="fullscreen-element"></div>
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[fullscreen] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[fullscreen] " + msg);
+}
+
+/*
+<html>
+ <body onload='document.body.requestFullscreen();'>
+ <iframe id='inner-frame'></iframe>
+ </body>
+</html>
+*/
+var iframeContents = "<html><body onload='parent.SimpleTest.waitForFocus(function(){document.body.requestFullscreen();});'><iframe id='inner-frame'></iframe></body></html>";
+
+var iframe = null;
+var outOfDocElement = null;
+var inDocElement = null;
+var container = null;
+var button = null;
+
+
+function sendMouseClick(element) {
+ synthesizeMouseAtCenter(element, {});
+}
+
+function assertPromiseResolved(promise, msg) {
+ let { state, value } = SpecialPowers.PromiseDebugging.getState(promise);
+ is(state, "fulfilled", "Promise should have been resolved " + msg);
+ is(value, undefined, "Promise should be resolved with undefined " + msg);
+}
+
+function assertPromiseRejected(promise, msg) {
+ let { state, reason } = SpecialPowers.PromiseDebugging.getState(promise);
+ is(state, "rejected", "Promise should have been rejected " + msg);
+ // XXX Actually we should be testing "instanceof TypeError", but it
+ // doesn't work as expected currently. See bug 1412856.
+ is(reason.name, "TypeError",
+ "Promise should be rejected with TypeError " + msg);
+}
+
+const FULLSCREEN_ELEMENT = document.getElementById("fullscreen-element");
+let promise;
+
+function enter1(event) {
+ is(event.target, FULLSCREEN_ELEMENT,
+ "Event target should be the fullscreen element #1");
+ ok(document.fullscreen, "Document should be in fullscreen");
+ is(document.fullscreenElement, FULLSCREEN_ELEMENT,
+ "Full-screen element should be div element.");
+ ok(document.fullscreenElement.matches(":fullscreen"),
+ "FSE should match :fullscreen");
+ addFullscreenChangeContinuation("exit", exit1);
+ FULLSCREEN_ELEMENT.remove();
+ is(document.fullscreenElement, null,
+ "Full-screen element should be null after removing.");
+}
+
+function exit1(event) {
+ document.body.appendChild(FULLSCREEN_ELEMENT);
+ is(document.fullscreenElement, null,
+ "Full-screen element should still be null after re-adding former FSE.");
+ is(event.target, document, "Event target should be the document #2");
+ ok(!document.fullscreen, "Document should not be in fullscreen");
+ is(document.fullscreenElement, null, "Full-screen element should be null.");
+ iframe = document.createElement("iframe");
+ iframe.allowFullscreen = true;
+ addFullscreenChangeContinuation("enter", enter2);
+ document.body.appendChild(iframe);
+ iframe.srcdoc = iframeContents;
+}
+
+function enter2(event) {
+ is(event.target, iframe,
+ "Event target should be the fullscreen iframe #3");
+ is(document.fullscreenElement, iframe,
+ "Full-screen element should be iframe element.");
+ is(iframe.contentDocument.fullscreenElement, iframe.contentDocument.body,
+ "Full-screen element in subframe should be body");
+
+ // The iframe's body is full-screen. Cancel full-screen in the subdocument to return
+ // the full-screen element to the previous full-screen element. This causes
+ // a fullscreenchange event.
+ addFullscreenChangeContinuation("exit", exit2);
+ promise = document.exitFullscreen();
+}
+
+function exit2(event) {
+ is(document.fullscreenElement, null,
+ "Full-screen element should have rolled back.");
+ is(iframe.contentDocument.fullscreenElement, null,
+ "Full-screen element in subframe should be null");
+ assertPromiseResolved(promise, "in exit2");
+
+ addFullscreenChangeContinuation("enter", enter3);
+ promise = FULLSCREEN_ELEMENT.requestFullscreen();
+}
+
+function enter3(event) {
+ is(event.target, FULLSCREEN_ELEMENT,
+ "Event target should be the fullscreen element #3");
+ is(document.fullscreenElement, FULLSCREEN_ELEMENT,
+ "Full-screen element should be div.");
+ assertPromiseResolved(promise, "in enter3");
+
+ // Transplant the FSE into subdoc. Should exit full-screen.
+ addFullscreenChangeContinuation("exit", exit3);
+ var _innerFrame = iframe.contentDocument.getElementById("inner-frame");
+ _innerFrame.contentDocument.body.appendChild(FULLSCREEN_ELEMENT);
+ is(document.fullscreenElement, null,
+ "Full-screen element transplanted, should be null.");
+ is(iframe.contentDocument.fullscreenElement, null,
+ "Full-screen element in outer frame should be null.");
+ is(_innerFrame.contentDocument.fullscreenElement, null,
+ "Full-screen element in inner frame should be null.");
+}
+
+function exit3(event) {
+ document.body.appendChild(FULLSCREEN_ELEMENT);
+ is(event.target, document, "Event target should be the document #3");
+ is(document.fullscreenElement, null, "Full-screen element should be null.");
+ document.body.removeChild(iframe);
+ iframe = null;
+
+ // Do a request out of document. It should be denied.
+ // Continue test in the following fullscreenerror handler.
+ outOfDocElement = document.createElement("div");
+ addFullscreenErrorContinuation(error1);
+ promise = outOfDocElement.requestFullscreen();
+}
+
+function error1(event) {
+ ok(!document.fullscreenElement,
+ "Requests for full-screen from not-in-doc elements should fail.");
+ assertPromiseRejected(promise, "in error1");
+ container = document.createElement("div");
+ inDocElement = document.createElement("div");
+ container.appendChild(inDocElement);
+ FULLSCREEN_ELEMENT.appendChild(container);
+
+ addFullscreenChangeContinuation("enter", enter4);
+ inDocElement.requestFullscreen();
+}
+
+function enter4(event) {
+ is(event.target, inDocElement,
+ "Event target should be the fullscreen element #4");
+ is(document.fullscreenElement, inDocElement, "FSE should be inDocElement.");
+
+ // Remove full-screen ancestor element from document, verify it stops being reported as current FSE.
+ addFullscreenChangeContinuation("exit", exit_to_arg_test_1);
+ container.remove();
+ is(document.fullscreenElement, null,
+ "Should not have a full-screen element again.");
+}
+
+async function exit_to_arg_test_1(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (third time).");
+ addFullscreenChangeContinuation("enter", enter_from_arg_test_1);
+ var threw = false;
+ try {
+ await FULLSCREEN_ELEMENT.requestFullscreen(123);
+ } catch (e) {
+ threw = true;
+ // trigger normal fullscreen so that we continue
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ }
+ ok(!threw, "requestFullscreen with bogus arg (123) shouldn't throw exception");
+}
+
+function enter_from_arg_test_1(event) {
+ ok(document.fullscreenElement,
+ "Should have entered full-screen after calling with bogus (ignored) argument (fourth time)");
+ addFullscreenChangeContinuation("exit", exit_to_arg_test_2);
+ document.exitFullscreen();
+}
+
+async function exit_to_arg_test_2(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (fourth time).");
+ addFullscreenChangeContinuation("enter", enter_from_arg_test_2);
+ var threw = false;
+ try {
+ await FULLSCREEN_ELEMENT.requestFullscreen({ vrDisplay: null });
+ } catch (e) {
+ threw = true;
+ // trigger normal fullscreen so that we continue
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ }
+ ok(!threw, "requestFullscreen with { vrDisplay: null } shouldn't throw exception");
+}
+
+function enter_from_arg_test_2(event) {
+ ok(document.fullscreenElement,
+ "Should have entered full-screen after calling with vrDisplay null argument (fifth time)");
+ addFullscreenChangeContinuation("exit", exit4);
+ document.exitFullscreen();
+}
+
+function exit4(event) {
+ ok(!document.fullscreenElement,
+ "Should be back in non-full-screen mode (fifth time)");
+ SpecialPowers.pushPrefEnv({"set":[["full-screen-api.allow-trusted-requests-only", true]]}, function() {
+ addFullscreenErrorContinuation(error2);
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ });
+}
+
+function error2(event) {
+ ok(!document.fullscreenElement,
+ "Should still be in normal mode, because calling context isn't trusted.");
+ button = document.createElement("button");
+ button.onclick = function() {
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ };
+ FULLSCREEN_ELEMENT.appendChild(button);
+ addFullscreenChangeContinuation("enter", enter5);
+ sendMouseClick(button);
+}
+
+function enter5(event) {
+ ok(document.fullscreenElement, "Moved to full-screen after mouse click");
+ addFullscreenChangeContinuation("exit", exit5);
+ document.exitFullscreen();
+}
+
+function exit5(event) {
+ ok(!document.fullscreenElement,
+ "Should have left full-screen mode (last time).");
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.enabled", false]]}, function() {
+ is(document.fullscreenEnabled, false, "document.fullscreenEnabled should be false if full-screen-api.enabled is false");
+ addFullscreenErrorContinuation(error3);
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ });
+}
+
+function error3(event) {
+ ok(!document.fullscreenElement,
+ "Should still be in normal mode, because pref is not enabled.");
+
+ SpecialPowers.pushPrefEnv({"set":[["full-screen-api.enabled", true]]}, function() {
+ is(document.fullscreenEnabled, true, "document.fullscreenEnabled should be true if full-screen-api.enabled is true");
+ opener.nextTest();
+ });
+}
+
+function begin() {
+ testNamespaces(() => {
+ addFullscreenChangeContinuation("enter", enter1);
+ FULLSCREEN_ELEMENT.requestFullscreen();
+ });
+}
+
+function testNamespaces(followupTestFn) {
+ let tests = [
+ {allowed: false, name: "element", ns: "http://www.w3.org/XML/1998/namespace"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/1999/xlink"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/2000/svg"},
+ {allowed: false, name: "element", ns: "http://www.w3.org/1998/Math/MathML"},
+ {allowed: false, name: "mathml", ns: "unknown"},
+ {allowed: false, name: "svg", ns: "unknown"},
+ {allowed: true, name: "element", ns: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"},
+ {allowed: true, name: "element", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "svg", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "math", ns: "http://www.w3.org/1999/xhtml"},
+ {allowed: true, name: "svg", ns: "http://www.w3.org/2000/svg"},
+ {allowed: true, name: "math", ns: "http://www.w3.org/1998/Math/MathML"},
+ {allowed: true, name: "element"},
+ ];
+
+ function runNextNamespaceTest() {
+ let test = tests.shift();
+ if (!test) {
+ followupTestFn();
+ return;
+ }
+
+ let elem = test.ns ? document.createElementNS(test.ns, test.name) :
+ document.createElement(test.name);
+ document.body.appendChild(elem);
+
+ if (test.allowed) {
+ addFullscreenChangeContinuation("enter", () => {
+ ok(document.fullscreen, "Document should be in fullscreen");
+ is(document.fullscreenElement, elem,
+ `Element named '${test.name}' in this namespace should be allowed: ${test.ns}`);
+ addFullscreenChangeContinuation("exit", () => {
+ document.body.removeChild(elem);
+ runNextNamespaceTest();
+ });
+ document.exitFullscreen();
+ });
+ } else {
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ `Element named '${test.name}' in this namespace should not be allowed: ${test.ns}`);
+ document.body.removeChild(elem);
+ runNextNamespaceTest();
+ });
+ }
+
+ SimpleTest.waitForFocus(() => elem.requestFullscreen());
+ }
+
+ runNextNamespaceTest();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-async.html b/dom/base/test/fullscreen/file_fullscreen-async.html
new file mode 100644
index 0000000000..e9b4147124
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-async.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Test for Bug 1129227</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="file_fullscreen-utils.js"></script>
+<style>
+</style>
+<button>Async Request Fullscreen</button>
+<script>
+function ok(condition, msg) {
+ opener.ok(condition, "[async] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[async] " + msg);
+}
+
+function begin() {
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", true]]
+ }, startTest);
+}
+
+function startTest() {
+ let button = document.querySelector("button");
+ button.addEventListener("click", () => {
+ setTimeout(() => document.body.requestFullscreen(), 0);
+ });
+ addFullscreenChangeContinuation("enter", enteredFullscreen);
+ addFullscreenErrorContinuation(() => {
+ ok(false, "Failed to enter fullscreen");
+ exitedFullscreen();
+ });
+ synthesizeMouseAtCenter(button, {});
+}
+
+function enteredFullscreen() {
+ is(document.fullscreenElement, document.body, "Entered fullscreen");
+ addFullscreenChangeContinuation("exit", exitedFullscreen);
+ document.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ SpecialPowers.popPrefEnv(finish);
+}
+
+function finish() {
+ opener.nextTest();
+}
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-backdrop.html b/dom/base/test/fullscreen/file_fullscreen-backdrop.html
new file mode 100644
index 0000000000..27be77a6d1
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-backdrop.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1064843</title>
+ <style id="style"></style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ html {
+ overflow: hidden;
+ }
+ #placeholder {
+ height: 1000vh;
+ }
+ </style>
+</head>
+<body>
+<div id="fullscreen"></div>
+<div id="placeholder"></div>
+<script>
+
+const gStyle = document.getElementById("style");
+const gFullscreen = document.getElementById("fullscreen");
+
+function is(a, b, msg) {
+ opener.is(a, b, "[backdrop] " + msg);
+}
+
+function isnot(a, b, msg) {
+ opener.isnot(a, b, "[backdrop] " + msg);
+}
+
+function ok(cond, msg) {
+ opener.ok(cond, "[backdrop] " + msg);
+}
+
+function info(msg) {
+ opener.info("[backdrop] " + msg);
+}
+
+function synthesizeMouseAtWindowCenter() {
+ synthesizeMouseAtPoint(innerWidth / 2, innerHeight / 2, {});
+}
+
+const gFullscreenElementBackground = getComputedStyle(gFullscreen).background;
+
+function begin() {
+ info("The default background of window should be white");
+ assertWindowPureColor(window, "white");
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ gFullscreen.requestFullscreen();
+}
+
+function setBackdropStyle(style) {
+ gStyle.textContent = `#fullscreen::backdrop { ${style} }`;
+}
+
+function enterFullscreen() {
+ is(getComputedStyle(gFullscreen).background, gFullscreenElementBackground,
+ "Computed background of #fullscreen shouldn't be changed");
+
+ info("The default background of backdrop for fullscreen is black");
+ assertWindowPureColor(window, "black");
+
+ setBackdropStyle("background: green");
+ info("The background color of backdrop should be changed to green");
+ assertWindowPureColor(window, "green");
+
+ gFullscreen.style.background = "blue";
+ info("The blue fullscreen element should cover the backdrop");
+ assertWindowPureColor(window, "blue");
+
+ gFullscreen.style.background = "";
+ setBackdropStyle("display: none");
+ info("The white body should be shown when the backdrop is hidden");
+ assertWindowPureColor(window, "white");
+
+ setBackdropStyle("");
+ info("Content should return to black because we restore the backdrop");
+ assertWindowPureColor(window, "black");
+
+ gFullscreen.style.display = "none";
+ info("The backdrop should disappear with the fullscreen element");
+ assertWindowPureColor(window, "white");
+
+ gFullscreen.style.display = "";
+ setBackdropStyle("position: absolute");
+ info("Changing position shouldn't immediately affect the view");
+ assertWindowPureColor(window, "black");
+
+ window.scroll(0, screen.height);
+ info("Scrolled up the absolutely-positioned element");
+ assertWindowPureColor(window, "white");
+
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ opener.nextTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html b/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html
new file mode 100644
index 0000000000..61db80c228
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-bug-1798219-2.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<button>Launch</button>
+<script>
+let button = document.querySelector("button");
+button.addEventListener("click", function(e) {
+ let newWindow = window.open("", "", "newWindow");
+ newWindow.document.write(`<!DOCTYPE HTML>
+ <button>click me!</button>
+ <script>
+ let button = document.querySelector("button");
+ button.addEventListener("click", function(e) {
+ document.documentElement.requestFullscreen();
+ setTimeout(() => {
+ while(true) {
+ // slowdown event loop
+ };
+ }, 1);
+ location.href = "https://example.org/browser/dom/base/test/fullscreen/dummy_page.html";
+ });
+ <\/script>`);
+});
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html b/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html
new file mode 100644
index 0000000000..7490f12936
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-bug-1798219.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<button>click me!</button>
+<script>
+let button = document.querySelector("button");
+button.addEventListener("click", function(e) {
+ document.documentElement.requestFullscreen();
+ setTimeout(() => {
+ while(true) {
+ // slowdown event loop
+ };
+ }, 1);
+ location.href = "https://example.org/browser/dom/base/test/fullscreen/dummy_page.html";
+});
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-denied-inner.html b/dom/base/test/fullscreen/file_fullscreen-denied-inner.html
new file mode 100644
index 0000000000..6b5916b2e2
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-denied-inner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="doRequestFullscreen()">
+<script>
+function doRequestFullscreen() {
+ function handler(evt) {
+ document.removeEventListener("fullscreenchange", handler);
+ document.removeEventListener("fullscreenerror", handler);
+ parent.is(evt.type, "fullscreenerror", "Request from " +
+ `document inside ${parent.testTargetName} should be denied`);
+ parent.continueTest();
+ }
+ parent.ok(!document.fullscreenEnabled, "Fullscreen " +
+ `should not be enabled in ${parent.testTargetName}`);
+ document.addEventListener("fullscreenchange", handler);
+ document.addEventListener("fullscreenerror", handler);
+ document.documentElement.requestFullscreen();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-denied.html b/dom/base/test/fullscreen/file_fullscreen-denied.html
new file mode 100644
index 0000000000..db9a69e71a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-denied.html
@@ -0,0 +1,171 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=545812
+
+Test DOM fullscreen API.
+
+-->
+<head>
+ <title>Test for Bug 545812</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[denied] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[denied] " + msg);
+}
+
+const INNER_FILE = "file_fullscreen-denied-inner.html";
+function setupForInnerTest(targetName, callback) {
+ window.testTargetName = targetName;
+ window.continueTest = () => {
+ delete window.testTargetName;
+ delete window.continueTest;
+ callback();
+ };
+}
+
+function begin() {
+ document.addEventListener("fullscreenchange", () => {
+ ok(false, "Should never receive " +
+ "a fullscreenchange event in the main window.");
+ });
+ SimpleTest.executeSoon(testIFrameWithoutAllowFullscreen);
+}
+
+function testIFrameWithoutAllowFullscreen() {
+ // Create an iframe without an allowfullscreen attribute, whose
+ // contents request fullscreen. The request should be denied, and
+ // we should not receive a fullscreenchange event in this document.
+ var iframe = document.createElement("iframe");
+ iframe.src = INNER_FILE;
+ // The iframe is same-origin so when we use feature policy otherwise we'd hit
+ // the "allowed" code-path (as intended). It is a bug that this test passes
+ // without the allow attribute.
+ iframe.allow = "fullscreen 'none'";
+ setupForInnerTest("an iframe without allowfullscreen", () => {
+ document.body.removeChild(iframe);
+ SimpleTest.executeSoon(testFrameElement);
+ });
+ document.body.appendChild(iframe);
+}
+
+function testFrameElement() {
+ var frameset = document.createElement("frameset");
+ var frame = document.createElement("frame");
+ frame.src = INNER_FILE;
+ frameset.appendChild(frame);
+ setupForInnerTest("a frame element", () => {
+ document.documentElement.removeChild(frameset);
+ SimpleTest.executeSoon(testObjectElement);
+ });
+ document.documentElement.appendChild(frameset);
+}
+
+function testObjectElement() {
+ var objectElem = document.createElement("object");
+ objectElem.data = INNER_FILE;
+ setupForInnerTest("an object element", () => {
+ document.body.removeChild(objectElem);
+ // In the following tests we want to test trust context requirement
+ // of fullscreen request, so temporary re-enable this pref.
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", true]]
+ }, testNonTrustContext);
+ });
+ document.body.appendChild(objectElem);
+}
+
+function testNonTrustContext() {
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ "Should not grant request in non-trust context.");
+ SimpleTest.executeSoon(testLongRunningEventHandler);
+ });
+ document.documentElement.requestFullscreen();
+}
+
+function testLongRunningEventHandler() {
+ let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+
+ function longRunningHandler() {
+ window.removeEventListener("keypress", longRunningHandler);
+ // Busy loop until transient useractivation is timed out, so our request for
+ // fullscreen should be rejected.
+ var end = (new Date()).getTime() + timeout;
+ while ((new Date()).getTime() < end) {
+ ; // Wait...
+ }
+ document.documentElement.requestFullscreen();
+ }
+ addFullscreenErrorContinuation(() => {
+ ok(!document.fullscreenElement,
+ "Should not grant request in long-running event handler.");
+ SimpleTest.executeSoon(testFullscreenMouseBtn);
+ });
+ window.addEventListener("keypress", longRunningHandler);
+ sendString("a");
+}
+
+function requestFullscreenMouseBtn(event, button) {
+ let clickEl = document.createElement("p");
+ clickEl.innerText = "Click Me";
+
+ function eventHandler(evt) {
+ document.body.requestFullscreen();
+ evt.target.removeEventListener(evt, this);
+ }
+
+ clickEl.addEventListener(event, eventHandler);
+ document.body.appendChild(clickEl);
+ synthesizeMouseAtCenter(clickEl, { button });
+}
+
+async function testFullscreenMouseBtn(event, button, next) {
+ await SpecialPowers.pushPrefEnv({
+ "set": [["full-screen-api.mouse-event-allow-left-button-only", true]]
+ });
+ let fsRequestEvents = ["mousedown", "mouseup", "pointerdown", "pointerup"];
+ let mouseButtons = [1, 2];
+
+ for (let i = 0; i < fsRequestEvents.length; i++) {
+ let evt = fsRequestEvents[i];
+ for (let j = 0; j < mouseButtons.length; j++) {
+ let mouseButton = mouseButtons[j];
+ await new Promise(resolve => {
+ addFullscreenErrorContinuation(resolve);
+ requestFullscreenMouseBtn(evt, mouseButton);
+ });
+ ok(!document.fullscreenElement, `Should not grant request on '${evt}' triggered by mouse button ${mouseButton}`);
+ }
+ }
+ // Restore the pref environment we changed before
+ // entering testNonTrustContext.
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+ finish();
+}
+
+function finish() {
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html b/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html
new file mode 100644
index 0000000000..d7d8a90aaf
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-esc-exit-inner.html
@@ -0,0 +1,58 @@
+ <!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verify that an ESC key press in a subdoc of a full-screen doc causes us to
+exit DOM full-screen mode.
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ body:not(:fullscreen) {
+ background-color: blue;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 700764 **/
+
+function ok(condition, msg) {
+ parent.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ parent.is(a, b, msg);
+}
+
+var escKeyReceived = false;
+var escKeySent = false;
+
+function keyHandler(event) {
+ if (escKeyReceived == KeyboardEvent.DOM_VK_ESC) {
+ escKeyReceived = true;
+ }
+}
+
+window.addEventListener("keydown", keyHandler, true);
+window.addEventListener("keyup", keyHandler, true);
+window.addEventListener("keypress", keyHandler, true);
+
+function startTest() {
+ ok(!document.fullscreenElement, "Subdoc should not be in full-screen mode");
+ ok(parent.document.fullscreenElement, "Parent should be in full-screen mode");
+ escKeySent = true;
+ window.focus();
+ synthesizeKey("KEY_Escape");
+}
+
+</script>
+</pre>
+<p>Inner frame</p>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-esc-exit.html b/dom/base/test/fullscreen/file_fullscreen-esc-exit.html
new file mode 100644
index 0000000000..f65f930b3f
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-esc-exit.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verify that an ESC key press in a subdoc of a full-screen doc causes us to
+exit DOM full-screen mode.
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body:fullscreen, div:fullscreen {
+ background-color: red;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+ opener.ok(condition, "[esc-exit] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[esc-exit] " + msg);
+}
+
+function finish() {
+ opener.nextTest();
+}
+
+function fullscreenchange1(event) {
+ is(document.fullscreenElement, document.body, "FSE should be doc");
+ addFullscreenChangeContinuation("exit", fullscreenchange2);
+ ok(!document.getElementById("subdoc").contentWindow.escKeySent, "Should not yet have sent ESC key press.");
+ document.getElementById("subdoc").contentWindow.startTest();
+}
+
+function fullscreenchange2(event) {
+ ok(document.getElementById("subdoc").contentWindow.escKeySent, "Should have sent ESC key press.");
+ ok(!document.getElementById("subdoc").contentWindow.escKeyReceived, "ESC key press to exit should not be delivered.");
+ ok(!document.fullscreenElement, "Should have left full-screen mode on ESC key press");
+ finish();
+}
+
+function begin() {
+ addFullscreenChangeContinuation("enter", fullscreenchange1);
+ document.body.requestFullscreen();
+}
+
+</script>
+
+<!-- This subframe conducts the test. -->
+<iframe id="subdoc" src="file_fullscreen-esc-exit-inner.html"></iframe>
+
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-event-order.html b/dom/base/test/fullscreen/file_fullscreen-event-order.html
new file mode 100644
index 0000000000..72fb2c9b47
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-event-order.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="file_fullscreen-utils.js"></script>
+<iframe src="empty.html" allowfullscreen></iframe>
+<script>
+function ok(condition, msg) {
+ opener.ok(condition, "[event-order] " + msg);
+}
+function is(a, b, msg) {
+ opener.is(a, b, "[event-order] " + msg);
+}
+
+let fullscreenEvents = [];
+let iframe, iframeDoc;
+
+function begin() {
+ iframe = document.querySelector("iframe");
+ iframeDoc = iframe.contentDocument;
+ document.addEventListener("fullscreenchange", evt => {
+ fullscreenEvents.push(evt);
+ });
+ iframeDoc.addEventListener("fullscreenchange", evt => {
+ fullscreenEvents.push(evt);
+ });
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ iframeDoc.body.requestFullscreen();
+}
+
+function assertFullscreenEvents(action) {
+ is(fullscreenEvents.length, 2,
+ "Two documents should have event dispatched for " + action);
+ is(fullscreenEvents[0].target, iframe,
+ "Root document should have the event dispatched first after " + action);
+ is(fullscreenEvents[1].target, iframeDoc.body,
+ "Inner document should have the event dispatched second after " + action);
+}
+
+function enterFullscreen() {
+ assertFullscreenEvents("requestFullscreen");
+ fullscreenEvents = [];
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ assertFullscreenEvents("exitFullscreen");
+ opener.nextTest();
+}
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html b/dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html
new file mode 100644
index 0000000000..844684b054
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-featurePolicy-inner.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="doRequestFullscreen()">
+<script>
+function doRequestFullscreen() {
+ let isChrome = location.search.includes("chrome");
+
+ function handler(evt) {
+ document.removeEventListener("fullscreenchange", handler);
+ document.removeEventListener("fullscreenerror", handler);
+ const enabled = isChrome ? SpecialPowers.wrap(document).fullscreenEnabled
+ : document.fullscreenEnabled;
+ if (evt.type == "fullscreenchange") {
+ document.addEventListener("fullscreenchange", () => parent.continueTest(evt.type, enabled), {once: true});
+ document.exitFullscreen();
+ } else {
+ parent.continueTest(evt.type, enabled);
+ }
+ }
+ document.addEventListener("fullscreenchange", handler);
+ document.addEventListener("fullscreenerror", handler);
+ parent.opener.info("Requesting fullscreen");
+ if (isChrome) {
+ SpecialPowers.wrap(document.documentElement).requestFullscreen();
+ } else {
+ document.documentElement.requestFullscreen();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-featurePolicy.html b/dom/base/test/fullscreen/file_fullscreen-featurePolicy.html
new file mode 100644
index 0000000000..c8b943c612
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-featurePolicy.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FeaturePolicy + fullscreen</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+
+<script type="application/javascript">
+function ok(condition, msg) {
+ opener.ok(condition, "[featurePolicy] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[featurePolicy] " + msg);
+}
+
+const INNER_FILE = "file_fullscreen-featurePolicy-inner.html";
+
+function begin() {
+ nextTest();
+}
+
+var tests = [
+ ["fullscreen 'none'", "fullscreenerror"],
+ ["fullscreen", "fullscreenchange"],
+ ["fullscreen 'src'", "fullscreenchange"],
+ ["fullscreen 'self'", "fullscreenchange"],
+ ["fullscreen *", "fullscreenchange"],
+ ["fullscreen http://random.net", "fullscreenerror"],
+ [null, "fullscreenchange"],
+];
+
+async function nextTest() {
+ if (!tests.length) {
+ opener.nextTest();
+ return;
+ }
+
+ let [value, expectedEvent] = tests.shift();
+
+ for (const isChrome of [false, true]) {
+ opener.info(`Running ${value} ${isChrome ? "w/" : "wo/"} chrome privileges`);
+
+ // Create an iframe with an allowfullscreen and with an allow attribute.
+ // The request should be denied or allowed, based on the current test.
+ const iframe = document.createElement("iframe");
+ iframe.setAttribute("allowfullscreen", "true");
+ if (value) {
+ iframe.setAttribute("allow", value);
+ }
+ iframe.src = INNER_FILE + (isChrome ? "?chrome" : "");
+
+ const setupForInnerTest = targetName => {
+ window.testTargetName = targetName;
+ return new Promise(resolve => {
+ window.continueTest = (event, enabled) => {
+ delete window.testTargetName;
+ delete window.continueTest;
+ resolve({ event, enabled });
+ };
+ document.body.appendChild(iframe);
+ });
+ };
+
+ const { event, enabled } = await setupForInnerTest(
+ `an iframe+allowfullscreen+allow:${value}+isChrome:${isChrome}`
+ );
+
+ if (isChrome) {
+ is(event, "fullscreenchange", "Expected a fullscreenchange event");
+ ok(enabled, "Should be enabled in chrome");
+ } else {
+ is(event, expectedEvent, "Expected a " + expectedEvent + " event");
+ is(enabled, expectedEvent == "fullscreenchange", "Should be appropriately enabled");
+ }
+ iframe.remove();
+ }
+ SimpleTest.executeSoon(nextTest);
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-focus-inner.html b/dom/base/test/fullscreen/file_fullscreen-focus-inner.html
new file mode 100644
index 0000000000..73d39a9d83
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-focus-inner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Focus test - child window</title>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function enterFullscreen() {
+ addFullscreenErrorContinuation(() => { opener.enteredFullscreen(false); });
+
+ addFullscreenChangeContinuation("enter", () => {
+ opener.enteredFullscreen(true);
+ });
+
+ document.body.requestFullscreen();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-focus.html b/dom/base/test/fullscreen/file_fullscreen-focus.html
new file mode 100644
index 0000000000..be91025f45
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-focus.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+
+Test that a fullscreen request fails if the window is not focused.
+
+Open window1, open window2, focus window2, and then attempt to fullscreen
+window1 while it is not focused. The fullscreen attempt should be rejected
+because the window is not focused.
+
+-->
+<head>
+ <title>Test fullscreen request is blocked when window is not focused</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+ opener.ok(condition, "[focus] " + msg);
+}
+
+var window1, window2;
+
+function openWindow() {
+ var w = window.open("file_fullscreen-focus-inner.html", "",
+ "width=500,height=500");
+ return w;
+}
+
+function begin() {
+ window1 = openWindow();
+ window1.focus();
+
+ SimpleTest.waitForFocus(function(){
+ window2 = openWindow();
+ window2.focus();
+
+ SimpleTest.waitForFocus(function(){
+ // Now that window2 is focused, attempt to fullscreen window1.
+ // This should fail.
+ window1.enterFullscreen("one");
+ }, window2);
+
+ }, window1);
+}
+
+async function enteredFullscreen(enteredSuccessfully) {
+ ok(!enteredSuccessfully, "window1 did not enter fullscreen");
+
+ if (enteredSuccessfully) {
+ await window1.document.exitFullscreen()
+ }
+
+ window1.close();
+ window2.close();
+ opener.nextTest();
+}
+
+</script>
+</pre>
+<div id="full-screen-element"></div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-hidden.html b/dom/base/test/fullscreen/file_fullscreen-hidden.html
new file mode 100644
index 0000000000..bd8c8189c9
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-hidden.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=697636
+-->
+<head>
+ <title>Test for Bug 697636</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<iframe id="f" srcdoc="<body text=green>1" allowfullscreen></iframe>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=697636">Mozilla Bug 697636</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 697636 **/
+
+var frameWin;
+var e1;
+
+function begin()
+{
+ var f = document.getElementById("f");
+ frameWin = f.contentWindow;
+ e1 = frameWin.document.documentElement;
+ f.srcdoc = "<body text=blue onload='parent.b2()'>2";
+}
+
+function b2()
+{
+ try {
+ e1.requestFullscreen();
+ } catch(e) {
+ opener.ok(false, "[hidden] Should not enter full-screen");
+ }
+ setTimeout(done, 0);
+}
+
+function done() {
+ opener.ok(!document.fullscreenElement, "[hidden] Should not have entered full-screen mode in hidden document.");
+ opener.ok(!e1.ownerDocument.fullscreenElement, "[hidden] Requesting owner should not have entered full-screen mode.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html b/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html
new file mode 100644
index 0000000000..4a614fdecf
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html
@@ -0,0 +1,5 @@
+<html onclick="div.requestFullscreen()">
+<body>
+<div name="div" id="div" style="width: 100px; height: 100px; background: green;"></div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html b/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html
new file mode 100644
index 0000000000..b60dea43bf
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html
@@ -0,0 +1,5 @@
+<div name="div" id="div" style="width: 100px; height: 100px; background: blue;">
+<iframe id="iframe" allowfullscreen="yes"
+ src="http://example.org/browser/dom/base/test/fullscreen/file_fullscreen-iframe-inner.html">
+</iframe>
+</div><br>
diff --git a/dom/base/test/fullscreen/file_fullscreen-iframe-top.html b/dom/base/test/fullscreen/file_fullscreen-iframe-top.html
new file mode 100644
index 0000000000..dddf4930c2
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-iframe-top.html
@@ -0,0 +1,5 @@
+<div name="div" id="div" style="width: 100px; height: 100px; background: red;">
+<iframe id="iframe" allowfullscreen="yes"
+ src="http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html">
+</iframe>
+</div><br>
diff --git a/dom/base/test/fullscreen/file_fullscreen-lenient-setters.html b/dom/base/test/fullscreen/file_fullscreen-lenient-setters.html
new file mode 100644
index 0000000000..02491c177e
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-lenient-setters.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1268798</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+function ok(condition, msg) {
+ opener.ok(condition, "[lenient-setters] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[lenient-setters] " + msg);
+}
+
+function info(msg) {
+ opener.info("[lenient-setters] " + msg);
+}
+
+let unattachedDiv = document.createElement("div");
+
+function begin() {
+ var originalValue = document.fullscreen;
+ try {
+ document.fullscreen = !document.fullscreen;
+ is(document.fullscreen, originalValue,
+ "fullscreen should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreen should not throw");
+ }
+
+ var originalElem = document.fullscreenElement;
+ try {
+ document.fullscreenElement = unattachedDiv;
+ document.fullscreenElement = [];
+ is(document.fullscreenElement, originalElem,
+ "fullscreenElement should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreenElement should not throw");
+ }
+
+ var originalEnabled = document.fullscreenEnabled;
+ try {
+ document.fullscreenEnabled = !originalEnabled;
+ is(document.fullscreenEnabled, originalEnabled,
+ "fullscreenEnabled should not be changed");
+ } catch (e) {
+ ok(false, "Setting fullscreenEnabled should not throw");
+ }
+
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-multiple-inner.html b/dom/base/test/fullscreen/file_fullscreen-multiple-inner.html
new file mode 100644
index 0000000000..cb5ca9b28e
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-multiple-inner.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 724554</title>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+function begin(id) {
+ opener.addFullscreenErrorContinuation(function() {
+ opener.ok(false, "Fullscreen denied " + id);
+ }, document);
+ opener.addFullscreenChangeContinuation("enter",
+ function() {
+ opener.enteredFullscreen(id);
+ }, document);
+ document.body.requestFullscreen();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-multiple.html b/dom/base/test/fullscreen/file_fullscreen-multiple.html
new file mode 100644
index 0000000000..f9e35b5e78
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-multiple.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=724554
+
+Test that multiple windows can be fullscreen at the same time.
+
+Open one window, focus it and enter fullscreen, then open another, focus
+it and enter fullscreen, and check that both are still fullscreen.
+
+-->
+<head>
+ <title>Test for Bug 724554</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<script type="application/javascript">
+
+/** Test for Bug 545812 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[multiple] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[multiple] " + msg);
+}
+
+var window1, window2;
+
+function openWindow(id) {
+ var w = window.open("file_fullscreen-multiple-inner.html", "", "width=500,height=500");
+ waitForLoadAndPaint(w, function() {
+ SimpleTest.waitForFocus(function() {
+ info(`Window ${id} is focused, starting test...`);
+ w.begin(id);
+ }, w);
+ w.focus();
+ });
+ return w;
+}
+
+function begin() {
+ window1 = openWindow("one");
+}
+
+function enteredFullscreen(id) {
+ if (id == "one") {
+ window2 = openWindow("two");
+ } else if (id == "two") {
+ ok(window1.document.fullscreenElement &&
+ window2.document.fullscreenElement,
+ "Both windows should be fullscreen concurrently");
+ window1.close();
+ window2.close();
+ opener.nextTest();
+ }
+}
+
+</script>
+</pre>
+<div id="full-screen-element"></div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-navigation.html b/dom/base/test/fullscreen/file_fullscreen-navigation.html
new file mode 100644
index 0000000000..9b68fedf9a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-navigation.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=685402
+-->
+<head>
+ <title>Test for Bug 685402</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="background-color: gray;">
+
+<iframe id="f" srcdoc="<body text=green>1" allowfullscreen></iframe>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685402">Mozilla Bug 685402</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 685402 **/
+
+var frameWin;
+var e1;
+var prevEnabled;
+var prevTrusted;
+
+function begin()
+{
+ var f = document.getElementById("f");
+ frameWin = f.contentWindow;
+ e1 = frameWin.document.body;
+ document.addEventListener("fullscreenchange", function() {
+ opener.ok(document.fullscreenElement, "[navigation] Request should be granted");
+ f.srcdoc = "<body text=blue onload='parent.b2()'>2";
+ }, {once: true});
+
+ e1.requestFullscreen();
+}
+
+function b2()
+{
+ opener.ok(!document.fullscreenElement, "[navigation] Should have left full-screen due to navigation.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-nested.html b/dom/base/test/fullscreen/file_fullscreen-nested.html
new file mode 100644
index 0000000000..1629d8386c
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-nested.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1187801</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<iframe src="empty.html" allowfullscreen></iframe>
+<script type="text/javascript">
+
+/** Test for Bug 1187801 **/
+
+function info(msg) {
+ opener.info("[nested] " + msg);
+}
+
+function ok(condition, msg) {
+ opener.ok(condition, "[nested] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[nested] " + msg);
+}
+
+var gInnerDoc;
+var gTestSteps;
+var gTestIndex = 0;
+
+function begin() {
+ var root = document.documentElement;
+ var iframe = document.querySelector("iframe");
+ var innerDoc = gInnerDoc = iframe.contentDocument;
+ var innerRoot = innerDoc.documentElement;
+
+ // The format of each test step is:
+ // [[action, target], [fsOuter, fsInner]] where:
+ // * "action" is "enter" or "exit" means whether we want to enter or
+ // fullscreen in this step. An action of "reset" means to force
+ // our count of enters to a certain value, to match how many exits
+ // we need to do to leave fullscreen. This is used when one exit
+ // can unwind more than one enter.
+ // * "target" is where we apply this action. For "enter" action, it
+ // is the element we want to call requestFullscreen() on, and for
+ // "exit", it is the document we want to call exitFullscreen() on.
+ // * "fsOuter" and "fsInner" are the expected fullscreen elements of
+ // the outer and inner document respectively after executing the
+ // action in this step. These are only checked after "enter" or
+ // "exit" actions.
+ gTestSteps = [
+ // innerRoot
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [ null, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ null, null]],
+ // root, innerRoot
+ [["enter", root], [ root, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", root], [ root, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ // iframe, innerRoot
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [iframe, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [["reset", 1], [ null, null]],
+ [[ "exit", document], [ null, null]],
+ // root, iframe, innerRoot
+ [["enter", root], [ root, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", innerDoc], [iframe, null]],
+ [[ "exit", document], [ root, null]],
+ [[ "exit", document], [ null, null]],
+ [["enter", root], [ root, null]],
+ [["enter", iframe], [iframe, null]],
+ [["enter", innerRoot], [iframe, innerRoot]],
+ [[ "exit", document], [ root, null]],
+ [["reset", 1], [ null, null]],
+ [[ "exit", document], [ null, null]],
+ ];
+
+ nextStep();
+}
+
+function nextStep() {
+ if (gTestIndex == gTestSteps.length) {
+ opener.nextTest();
+ return;
+ }
+
+ var index = gTestIndex;
+ var [[action, target], [fsOuter, fsInner]] = gTestSteps[gTestIndex++];
+
+ function checkAndNext() {
+ is(document.fullscreenElement, fsOuter,
+ `Fullscreen element of outer doc should match after step ${index}`);
+ is(gInnerDoc.fullscreenElement, fsInner,
+ `Fullscreen element of inner doc should match after step ${index}`);
+ nextStep();
+ }
+
+ info(`Executing step ${index}: ${action} on ${target}...`);
+ if (action == "enter") {
+ // For "enter" action, the target is the element
+ var doc = target.ownerDocument;
+ addFullscreenChangeContinuation("enter", checkAndNext, doc);
+ target.requestFullscreen();
+ } else if (action == "exit") {
+ // For "exit" action, the target is the document
+ addFullscreenChangeContinuation("exit", checkAndNext, target);
+ target.exitFullscreen();
+ } else if (action == "reset") {
+ // For "reset" action, the target is the number to setFullscreenChangeEnters.
+ setFullscreenChangeEnters(target);
+ nextStep();
+ } else {
+ ok(false, `Unknown action ${action}`);
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-newtab.html b/dom/base/test/fullscreen/file_fullscreen-newtab.html
new file mode 100644
index 0000000000..0eaf5dd546
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-newtab.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<a id="link" href="about:blank" target="_blank" rel="opener"
+ onclick="document.body.requestFullscreen()">Click here</a>
diff --git a/dom/base/test/fullscreen/file_fullscreen-prefixed.html b/dom/base/test/fullscreen/file_fullscreen-prefixed.html
new file mode 100644
index 0000000000..dfe1965365
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-prefixed.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 743198</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+ <div id="fullscreen"></div>
+<script>
+
+function ok(condition, msg) {
+ opener.ok(condition, "[prefixed] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[prefixed] " + msg);
+}
+
+function info(msg) {
+ opener.info("[prefixed] " + msg);
+}
+
+SimpleTest.requestFlakyTimeout(
+ "need to wait for a while to confirm no unexpected event is dispatched");
+
+let div = document.getElementById("fullscreen");
+let unattachedDiv = document.createElement('div');
+
+const NO_EVENT_HANDLER = 0;
+const PREFIXED_EVENT_ONLY = 1;
+const PREFIXED_AND_UNPREFIXED_EVENT = 2;
+
+class TestCase {
+ constructor(num, handlersOnWindow, handlersOnDocument) {
+ this.number = num;
+ this.handlersType = new Map([[window, handlersOnWindow],
+ [document, handlersOnDocument]]);
+ }
+
+ static checkState(inFullscreen, msg) {
+ var emptyOrNot = inFullscreen ? "" : "not ";
+ info(`Check fullscreen state ${msg}`);
+ is(document.mozFullScreen, inFullscreen,
+ `Should ${emptyOrNot}be in fullscreen`);
+ is(document.fullscreenElement, inFullscreen ? div : null,
+ `Fullscreen element should be ${inFullscreen ? "div" : "null"}`);
+ is(document.mozFullScreenElement, document.fullscreenElement,
+ "document.mozFullScreenElement should be identical to fullscreenElement");
+ is(div.matches(":fullscreen"), inFullscreen,
+ `Fullscreen element should ${emptyOrNot}match :fullscreen pseudo class`);
+ is(div.matches(":-moz-full-screen"), inFullscreen,
+ `Fullscreen element should ${emptyOrNot}match :-moz-full-screen pseudo class`);
+ }
+
+ changeListeners(action, eventType, handler) {
+ let method = `${action}EventListener`;
+ for (let [target, type] of this.handlersType.entries()) {
+ if (type == PREFIXED_EVENT_ONLY) {
+ target[method](`moz${eventType}`, handler);
+ } else if (type == PREFIXED_AND_UNPREFIXED_EVENT) {
+ target[method](eventType, handler);
+ target[method](`moz${eventType}`, handler);
+ } else if (type != NO_EVENT_HANDLER) {
+ ok(false, `Unknown handlers type ${type}`);
+ }
+ }
+ }
+
+ doTest(actionCallback, eventType, inFullscreen, msg) {
+ return new Promise(resolve => {
+ let timeout = 0;
+ let expectEvent = new Map();
+ for (let [target] of this.handlersType) {
+ expectEvent.set(target, this.handlersType != NO_EVENT_HANDLER);
+ }
+ let handleEvent = evt => {
+ let target = evt.currentTarget;
+ let type = this.handlersType.get(target);
+ if (type == PREFIXED_EVENT_ONLY) {
+ is(evt.type, `moz${eventType}`,
+ `Should get prefixed event on ${target}`);
+ } else if (type == PREFIXED_AND_UNPREFIXED_EVENT) {
+ is(evt.type, eventType,
+ `Should only get unprefixed event on ${target}`);
+ } else {
+ ok(false, `No event should be triggered on ${target}`);
+ }
+ // Ensure we receive each event exactly once.
+ if (expectEvent.get(target)) {
+ expectEvent.set(target, false);
+ } else {
+ ok(false, `Got an unexpected ${evt.type} event on ${target}`);
+ }
+ if (!timeout) {
+ timeout = setTimeout(() => {
+ this.changeListeners("remove", eventType, handleEvent);
+ TestCase.checkState(inFullscreen,
+ `${msg} in test case ${this.number}`);
+ resolve();
+ });
+ }
+ };
+ this.changeListeners("add", eventType, handleEvent);
+ SimpleTest.waitForFocus(() => actionCallback());
+ });
+ }
+
+ test() {
+ return new Promise(resolve => {
+ Promise.resolve().then(() => {
+ return this.doTest(() => div.mozRequestFullScreen(),
+ "fullscreenchange", true, "after request");
+ }).then(() => {
+ return this.doTest(() => document.mozCancelFullScreen(),
+ "fullscreenchange", false, "after exit");
+ }).then(() => {
+ return this.doTest(() => unattachedDiv.mozRequestFullScreen(),
+ "fullscreenerror", false, "after failed request");
+ }).then(resolve);
+ });
+ }
+}
+
+let gTestcases = [
+ new TestCase(1, PREFIXED_EVENT_ONLY, NO_EVENT_HANDLER),
+ new TestCase(2, PREFIXED_AND_UNPREFIXED_EVENT, NO_EVENT_HANDLER),
+ new TestCase(3, NO_EVENT_HANDLER, PREFIXED_EVENT_ONLY),
+ new TestCase(4, PREFIXED_EVENT_ONLY, PREFIXED_EVENT_ONLY),
+ new TestCase(5, PREFIXED_AND_UNPREFIXED_EVENT, PREFIXED_EVENT_ONLY),
+ new TestCase(6, NO_EVENT_HANDLER, PREFIXED_AND_UNPREFIXED_EVENT),
+ new TestCase(7, PREFIXED_EVENT_ONLY, PREFIXED_AND_UNPREFIXED_EVENT),
+ new TestCase(8, PREFIXED_AND_UNPREFIXED_EVENT, PREFIXED_AND_UNPREFIXED_EVENT),
+ ];
+
+function begin() {
+ TestCase.checkState(false, "at the beginning");
+ runNextTestCase();
+}
+
+function runNextTestCase() {
+ let testcase = gTestcases.shift();
+ if (!testcase) {
+ opener.nextTest();
+ return;
+ }
+ testcase.test().then(runNextTestCase);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-resize.html b/dom/base/test/fullscreen/file_fullscreen-resize.html
new file mode 100644
index 0000000000..3050ba0d5d
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-resize.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1742421
+-->
+<head>
+ <title>Test for Bug 1742421</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="file_fullscreen-utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="background-color: gray;">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1742421">Mozilla Bug 1742421</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1742421 **/
+
+function begin()
+{
+ addFullscreenChangeContinuation("enter", () => {
+ opener.info("[resize] Entered fullscreen");
+ // Do not use addFullscreenChangeContinuation for fullscreen exit given that
+ // it expects the window will be restored to the original size.
+ document.addEventListener("fullscreenchange", () => {
+ opener.ok(!document.fullscreenElement, "[resize] Should have left full-screen due to resize");
+ opener.nextTest();
+ }, { once: true });
+ window.resizeBy(100,100);
+ });
+ document.body.requestFullscreen();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-rollback.html b/dom/base/test/fullscreen/file_fullscreen-rollback.html
new file mode 100644
index 0000000000..b1578b39cd
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-rollback.html
@@ -0,0 +1,140 @@
+ <!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=700764
+
+Verifies that cancelFullScreen() rolls back to have the previous full-screen
+element full-screen.
+
+Tests:
+* Request full-screen in doc.
+* Request full-screen in doc on element not descended from full-screen element.
+* Cancel full-screen, FSE should rollback to previous FSE.
+* Request full-screen in subdoc.
+* Cancel full-screen in subdoc, doc should be full-screen.
+* Request full-screen in subdoc.
+* Removing FSE should fully-exit full-screen.
+
+
+-->
+<head>
+ <title>Test for Bug 700764</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<div id="fse">
+ <div id="fse-inner">
+ <iframe id="subdoc" allowfullscreen srcdoc="<html><body bgcolor='black'></body></html>"></iframe>
+ </div>
+</div>
+
+<div id="non-fse"></div>
+
+<script type="application/javascript">
+
+/** Test for Bug 700764 **/
+
+function ok(condition, msg) {
+ opener.ok(condition, "[rollback] " + msg);
+ if (!condition) {
+ opener.finish();
+ }
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[rollback] " + msg);
+ if (a != b) {
+ opener.finish();
+ }
+}
+
+function enterFullscreen(element, callback) {
+ addFullscreenChangeContinuation("enter", callback);
+ element.focus();
+ element.requestFullscreen();
+}
+
+function revertFullscreen(doc, callback) {
+ ok(doc.fullscreenElement != null, "Should only exit fullscreen on a fullscreen doc");
+ addFullscreenChangeContinuation("exit", callback, doc);
+ doc.exitFullscreen();
+}
+
+function e(id) {
+ return document.getElementById(id);
+}
+
+function requestFullscreen(element) {
+ element.focus();
+ element.requestFullscreen();
+}
+
+function begin() {
+ enterFullscreen(e("fse"), change1);
+}
+
+function change1() {
+ is(document.fullscreenElement, e("fse"), "Body should be FSE");
+ // Request full-screen from element not descendent from current FSE.
+ enterFullscreen(e("non-fse"), change2);
+}
+
+function change2() {
+ is(document.fullscreenElement, e("non-fse"), "FSE should be e('non-fse')");
+ revertFullscreen(document, change3);
+}
+
+function change3() {
+ is(document.fullscreenElement, e("fse"), "FSE should rollback to FSE.");
+ var iframe = e("subdoc");
+ enterFullscreen(iframe.contentDocument.body, change4);
+}
+
+function change4() {
+ var iframe = e("subdoc");
+ is(document.fullscreenElement, iframe, "Subdoc container should be FSE.");
+ is(iframe.contentDocument.fullscreenElement, iframe.contentDocument.body, "Subdoc body should be FSE in subdoc");
+ revertFullscreen(document, change5);
+}
+
+function change5() {
+ is(document.fullscreenElement, e("fse"), "FSE should rollback to FSE.");
+ revertFullscreen(document, change6);
+}
+
+function change6() {
+ is(document.fullscreenElement, null, "Should have left full-screen entirely");
+ enterFullscreen(e("fse"), change7);
+}
+
+function change7() {
+ is(document.fullscreenElement, e("fse"), "FSE should be e('fse')");
+ enterFullscreen(e("fse-inner"), change8);
+}
+
+function change8() {
+ var element = e('fse-inner');
+ is(document.fullscreenElement, element, "FSE should be e('fse-inner')");
+
+ // We're breaking out of two levels of fullscreen by removing the
+ // fullscreenElement. To make our helper functions work correctly,
+ // we set the fullscreenChangeEnters value to 1. This is a hack, but
+ // it is a hack that supports the expected behavior.
+ setFullscreenChangeEnters(1);
+ addFullscreenChangeContinuation("exit", change9);
+ info(`Removing FSE should exit fullscreen.`);
+ element.remove();
+}
+
+function change9() {
+ is(document.fullscreenElement, null, "Should have fully exited full-screen mode when removed FSE from doc");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-scrollbar.html b/dom/base/test/fullscreen/file_fullscreen-scrollbar.html
new file mode 100644
index 0000000000..05ab51431a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-scrollbar.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1201798</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ html, body, #measure {
+ width: 100%; height: 100%;
+ margin: 0px; border: 0px;
+ }
+ div {
+ margin: 0px; border: 0px;
+ }
+ #ref-outer { width: 100px; height: 100px; overflow: scroll; }
+ #ref-inner { width: 100%; height: 100%; }
+ </style>
+</head>
+<body>
+<div id="measure"></div>
+<div style="height: 1000vh; width: 1000vw;"></div>
+<div id="ref-outer">
+ <div id="ref-inner"></div>
+</div>
+<div id="fullscreen"></div>
+<script type="text/javascript">
+
+/** Test for Bug 1201798 */
+
+var info = msg => opener.info("[scrollbar] " + msg);
+var ok = (cond, msg) => opener.ok(cond, "[scrollbar] " + msg);
+var is = (a, b, msg) => opener.is(a, b, "[scrollbar] " + msg);
+
+var gVerticalScrollbarWidth, gHorizontalScrollbarWidth;
+var gMeasureDiv = document.getElementById("measure");
+var gFullscreenDiv = document.getElementById("fullscreen");
+
+function getMeasureRect() {
+ return gMeasureDiv.getBoundingClientRect();
+}
+
+function triggerFrameReconstruction() {
+ info("Triggering a force frame reconstruction");
+ var docElem = document.documentElement;
+ var wm = window.getComputedStyle(docElem).writingMode;
+ if (wm == "horizontal-tb") {
+ docElem.style.writingMode = "vertical-rl";
+ } else {
+ docElem.style.writingMode = "horizontal-tb";
+ }
+ docElem.getBoundingClientRect();
+}
+
+function assertHasScrollbars(elem) {
+ var rect = getMeasureRect();
+ info(`screen.width: ${screen.width}, screen.height: ${screen.height}`);
+ info(`rect.width: ${rect.width}, rect.height: ${rect.height}`);
+ ok(rect.width <= screen.width - gVerticalScrollbarWidth,
+ `Should have width less than or equal to ${screen.width - gVerticalScrollbarWidth} indicating vertical scrollbar when ${elem} is in fullscreen`);
+ ok(rect.height <= screen.height - gHorizontalScrollbarWidth,
+ `Should have height less than or equal to ${screen.height - gHorizontalScrollbarWidth} indicating horizontal scrollbar when ${elem} is in fullscreen`);
+}
+
+function assertHasNoScrollbars(elem) {
+ var rect = getMeasureRect();
+ info(`screen.width: ${screen.width}, screen.height: ${screen.height}`);
+ info(`rect.width: ${rect.width}, rect.height: ${rect.height}`);
+ is(rect.width, screen.width,
+ `Should not have vertical scrollbar when ${elem} is in fullscreen`);
+ is(rect.height, screen.height,
+ `Should not have horizontal scrollbar when ${elem} is in fullscreen`);
+}
+
+function checkScrollbars(elem, shouldHaveScrollbars) {
+ is(document.fullscreenElement, elem,
+ "Should only check the current fullscreen element");
+ var assertFunc = shouldHaveScrollbars ?
+ assertHasScrollbars : assertHasNoScrollbars;
+ assertFunc(elem);
+ triggerFrameReconstruction();
+ assertFunc(elem);
+}
+
+function begin() {
+ // Check for the use of overlay scrollbars. We can only get an accurate
+ // answer to our media query if we are Chrome-privileged. Otherwise, the
+ // media query will never match.
+ let wrappedWindow = SpecialPowers.wrap(window);
+ if (wrappedWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
+ // If overlay scrollbar is enabled, the scrollbar is not measurable,
+ // so we skip this test in that case.
+ info("Skip this test because of overlay scrollbar");
+ opener.nextTest();
+ return;
+ }
+
+ const outerElement = document.getElementById("ref-outer");
+ var rectOuter = outerElement.getBoundingClientRect();
+ var rectInner = document.getElementById("ref-inner").getBoundingClientRect();
+ info(`rectOuter: ${rectOuter.width} x ${rectOuter.height}`);
+ info(`rectInner: ${rectInner.width} x ${rectInner.height}`);
+ gVerticalScrollbarWidth = rectOuter.width - rectInner.width;
+ gHorizontalScrollbarWidth = rectOuter.height - rectInner.height;
+ ok(gVerticalScrollbarWidth != 0, "Should have vertical scrollbar");
+ ok(gHorizontalScrollbarWidth != 0, "Should have horizontal scrollbar");
+ info(`gVerticalScrollbarWidth: ${gVerticalScrollbarWidth}`);
+ info(`gHorizontalScrollbarWidth: ${gHorizontalScrollbarWidth}`);
+
+ // Remove the display of outerElement to simplify layout when window goes
+ // to fullscreen.
+ outerElement.style.display = "none";
+
+ info("Entering fullscreen on root");
+ addFullscreenChangeContinuation("enter", enteredFullscreenOnRoot);
+ document.documentElement.requestFullscreen();
+}
+
+function enteredFullscreenOnRoot() {
+ checkScrollbars(document.documentElement, true);
+ info("Entering fullscreen on div");
+ addFullscreenChangeContinuation("enter", enteredFullscreenOnDiv);
+ gFullscreenDiv.requestFullscreen();
+}
+
+function enteredFullscreenOnDiv() {
+ checkScrollbars(gFullscreenDiv, false);
+ info("Exiting fullscreen on div");
+ addFullscreenChangeContinuation("exit", exitedFullscreenOnDiv);
+ document.exitFullscreen();
+}
+
+function exitedFullscreenOnDiv() {
+ checkScrollbars(document.documentElement, true);
+ info("Exiting fullscreen on root");
+ addFullscreenChangeContinuation("exit", exitedFullscreenOnRoot);
+ document.exitFullscreen();
+}
+
+function exitedFullscreenOnRoot() {
+ opener.nextTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-selector.html b/dom/base/test/fullscreen/file_fullscreen-selector.html
new file mode 100644
index 0000000000..522f06f6fd
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-selector.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1199522</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ div {
+ position: fixed;
+ top: 20px; height: 50px;
+ opacity: 0.3;
+ border: 5px solid black;
+ box-sizing: border-box;
+ }
+ #fullscreen0 {
+ left: 50px; width: 50px;
+ background: #ff0000;
+ border-color: #800000;
+ }
+ #fullscreen1 {
+ left: 100px; width: 50px;
+ background: #00ff00;
+ border-color: #008000;
+ }
+ #fullscreen2 {
+ left: 150px; width: 50px;
+ background: #0000ff;
+ border-color: #000080;
+ }
+ </style>
+</head>
+<body>
+<script type="application/javascript">
+
+/** Test for Bug 1199522 **/
+
+function info(msg) {
+ opener.info("[selector] " + msg);
+}
+
+function ok(condition, msg) {
+ opener.ok(condition, "[selector] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[selector] " + msg);
+}
+
+function rectEquals(rect1, rect2) {
+ return rect1.x == rect2.x && rect1.y == rect2.y &&
+ rect1.width == rect2.width && rect1.height == rect2.height;
+}
+
+function getViewportRect() {
+ return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
+}
+
+var fullscreenElems = [];
+
+function checkFullscreenState(elem, hasState, viewportRect) {
+ var id = elem.id;
+ var rect = elem.getBoundingClientRect();
+ if (hasState) {
+ ok(elem.matches(":fullscreen"),
+ `${id} should match selector ":fullscreen"`);
+ ok(rectEquals(rect, viewportRect),
+ `The bounding rect of ${id} should match the viewport`);
+ } else {
+ ok(!elem.matches(":fullscreen"),
+ `${id} should not match selector ":fullscreen"`);
+ // Position might vary because if one of our ancestors is fullscreen it
+ // contains us.
+ is(rect.width, elem.initialRect.width,
+ `The width of ${id} should match its initial state`);
+ is(rect.height, elem.initialRect.height,
+ `The height of ${id} should match its initial state`);
+ }
+}
+
+function checkFullscreenStates(states) {
+ var viewportRect = getViewportRect();
+ fullscreenElems.forEach((elem, index) => {
+ checkFullscreenState(elem, states[index], viewportRect);
+ });
+}
+
+function begin() {
+ fullscreenElems.push(document.getElementById('fullscreen0'));
+ fullscreenElems.push(document.getElementById('fullscreen1'));
+ fullscreenElems.push(document.getElementById('fullscreen2'));
+
+ var viewportRect = getViewportRect();
+ for (var elem of fullscreenElems) {
+ var rect = elem.getBoundingClientRect();
+ var id = elem.id;
+ elem.initialRect = rect;
+ ok(!elem.matches(":fullscreen"),
+ `${id} should not match selector ":fullscreen"`);
+ ok(!rectEquals(elem.initialRect, viewportRect),
+ `The initial bounding rect of ${id} should not match the viewport`);
+ }
+
+ info("Entering fullscreen on fullscreen0");
+ addFullscreenChangeContinuation("enter", enter0);
+ fullscreenElems[0].requestFullscreen();
+}
+
+function enter0() {
+ checkFullscreenStates([true, false, false]);
+ info("Entering fullscreen on fullscreen1");
+ addFullscreenChangeContinuation("enter", enter1);
+ fullscreenElems[1].requestFullscreen();
+}
+
+function enter1() {
+ checkFullscreenStates([true, true, false]);
+ info("Entering fullscreen on fullscreen2");
+ addFullscreenChangeContinuation("enter", enter2);
+ fullscreenElems[2].requestFullscreen();
+}
+
+function enter2() {
+ checkFullscreenStates([true, true, true]);
+ info("Leaving fullscreen on fullscreen2");
+ addFullscreenChangeContinuation("exit", exit2);
+ document.exitFullscreen();
+}
+
+function exit2() {
+ checkFullscreenStates([true, true, false]);
+ info("Leaving fullscreen on fullscreen1");
+ addFullscreenChangeContinuation("exit", exit1);
+ document.exitFullscreen();
+}
+
+function exit1() {
+ checkFullscreenStates([true, false, false]);
+ info("Leaving fullscreen on fullscreen0");
+ addFullscreenChangeContinuation("exit", exit0);
+ document.exitFullscreen();
+}
+
+function exit0() {
+ checkFullscreenStates([false, false, false]);
+
+ info("Entering fullscreen on all elements");
+ var count = 0;
+ function listener() {
+ if (++count == 3) {
+ document.removeEventListener("fullscreenchange", listener);
+ // We bypassed our fullscreenchangeenters count since we didn't
+ // do our requests with a addFullscreenChangeContinuation, so we
+ // fix up the expected value now that we're done with this part
+ // of the test.
+ setFullscreenChangeEnters(1);
+ enterAll();
+ }
+ }
+ document.addEventListener("fullscreenchange", listener);
+ fullscreenElems[0].requestFullscreen();
+ fullscreenElems[1].requestFullscreen();
+ fullscreenElems[2].requestFullscreen();
+}
+
+function enterAll() {
+ checkFullscreenStates([true, true, true]);
+ info("Fully-exiting fullscreen");
+ addFullscreenChangeContinuation("exit", exitAll);
+ synthesizeKey("KEY_Escape");
+}
+
+function exitAll() {
+ checkFullscreenStates([false, false, false]);
+ opener.nextTest();
+}
+
+</script>
+</pre>
+<div id="fullscreen0">
+ <div id="fullscreen1">
+ <div id="fullscreen2">
+ </div>
+ </div>
+</div>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-shadowdom.html b/dom/base/test/fullscreen/file_fullscreen-shadowdom.html
new file mode 100644
index 0000000000..348e08ae87
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-shadowdom.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1430305
+ Bug 1430305 - Implement ShadowRoot.fullscreenElement
+ -->
+ <head>
+ <title>Bug 1430305</title>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script src="/tests/SimpleTest/EventUtils.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430305">
+ Mozilla Bug 1430305</a>
+
+ <div id="host"></div>
+
+ <pre id="test">
+ <script type="application/javascript">
+
+ function begin() {
+ var host = document.getElementById("host");
+ var shadowRoot = host.attachShadow({mode: "open"});
+ shadowRoot.innerHTML = "<div>test</div>";
+ var elem = shadowRoot.firstChild;
+ var gotFullscreenEvent = false;
+
+ document.addEventListener("fullscreenchange", function (e) {
+ if (document.fullscreenElement === host) {
+ is(shadowRoot.fullscreenElement, elem,
+ "Expected element entered fullsceen");
+ gotFullscreenEvent = true;
+ document.exitFullscreen();
+ } else {
+ opener.ok(gotFullscreenEvent, "Entered fullscreen as expected");
+ is(shadowRoot.fullscreenElement, null,
+ "Shouldn't have fullscreenElement anymore.");
+ is(document.fullscreenElement, null,
+ "Shouldn't have fullscreenElement anymore.");
+ opener.nextTest();
+ }
+ });
+ elem.requestFullscreen();
+ }
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-single.html b/dom/base/test/fullscreen/file_fullscreen-single.html
new file mode 100644
index 0000000000..2ebc58bdae
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-single.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+
+Open one window, focus it and enter fullscreen, then exit fullscreen.
+
+-->
+<head>
+ <title>Simple Fullscreen Enter and Exit Test</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+
+<div id="fullscreen-div"><p>Fullscreen div</p></div>
+
+<script type="application/javascript">
+
+function ok(condition, msg) {
+ opener.ok(condition, "[single] " + msg);
+}
+
+function is(value, expected, msg) {
+ opener.is(value, expected, "[single] " + msg);
+}
+
+function isnot(value, unexpected, msg) {
+ opener.isnot(value, unexpected, "[single] " + msg);
+}
+
+function info(msg) {
+ opener.info("[single] " + msg);
+}
+
+function windowResized() {
+ info(`Window resized to width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+}
+
+async function begin() {
+ window.addEventListener('resize', windowResized);
+
+ info(`Starting window width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+ let windowedWidth = window.innerWidth;
+ let windowedHeight = window.innerHeight;
+
+ info("Requesting fullscreen.");
+ let entryPromise = document.getElementById('fullscreen-div').requestFullscreen()
+ info("Fullscreen requested, waiting for promise to resolve.");
+
+ await entryPromise;
+
+ info("element.requestFullscreen() promise resolved.");
+ info(`Fullscreen window width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+ isnot(document.fullscreenElement, null, "document.fullscreenElement should exist.");
+ ok(window.fullScreen, "window.fullScreen");
+ isnot(windowedWidth, window.innerWidth, "window width should be changed.");
+ isnot(windowedHeight, window.innerHeight, "window height should be changed.");
+
+ info("Requesting fullscreen exit.");
+ let exitPromise = document.exitFullscreen()
+ info("Fullscreen exit requested, waiting for promise to resolve.");
+
+ await exitPromise;
+
+ info("document.exitFullscreen() promise resolved.");
+ info(`Restored window width: ${window.innerWidth}, height: ${window.innerHeight}.`);
+ is(document.fullscreenElement, null, "document.fullscreenElement should be null.");
+ ok(!window.fullScreen, "window.fullScreen should be false.");
+ is(window.innerWidth, windowedWidth, "window width should be restored.");
+ is(window.innerHeight, windowedHeight, "window height should be restored.");
+ opener.nextTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-sub-iframe.html b/dom/base/test/fullscreen/file_fullscreen-sub-iframe.html
new file mode 100644
index 0000000000..28b0235c87
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-sub-iframe.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>Test for Bug 1609180</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="file_fullscreen-utils.js"></script>
+<style>
+</style>
+<button>Request Fullscreen on sub iframe</button>
+<iframe src="dummy_page.html" allowfullscreen></iframe>
+<script>
+function ok(condition, msg) {
+ opener.ok(condition, "[sub-iframe] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[sub-iframe] " + msg);
+}
+
+function begin() {
+ SpecialPowers.pushPrefEnv({
+ "set":[["full-screen-api.allow-trusted-requests-only", true]]
+ }, startTest);
+}
+
+let doc;
+function startTest() {
+ let button = document.querySelector("button");
+ doc = document.querySelector("iframe").contentDocument;
+ button.addEventListener("click", () => {
+ doc.documentElement.requestFullscreen();
+ });
+ addFullscreenChangeContinuation("enter", enteredFullscreen, doc);
+ addFullscreenErrorContinuation(() => {
+ ok(false, "Failed to enter fullscreen");
+ exitedFullscreen();
+ }, doc);
+ synthesizeMouseAtCenter(button, {});
+}
+
+function enteredFullscreen() {
+ is(doc.fullscreenElement, doc.documentElement, "Entered fullscreen");
+ addFullscreenChangeContinuation("exit", exitedFullscreen, doc);
+ doc.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ SpecialPowers.popPrefEnv(finish);
+}
+
+function finish() {
+ opener.nextTest();
+}
+</script>
diff --git a/dom/base/test/fullscreen/file_fullscreen-svg-element.html b/dom/base/test/fullscreen/file_fullscreen-svg-element.html
new file mode 100644
index 0000000000..1dfc78aa1c
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-svg-element.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=735031
+ Bug 735031 - Fullscreen API implementation assumes an HTML Element
+ -->
+ <head>
+ <title>Bug 735031</title>
+ <script src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script src="/tests/SimpleTest/EventUtils.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=73503">
+ Mozilla Bug 735031</a>
+
+ <svg id="svg-elem" width="100" height="100" viewbox="0 0 100 100">
+ <rect x="10" y="10" width="50" height="50"
+ fill="black" stroke="blue" stroke-width="2"/>
+ </svg>
+
+ <pre id="test">
+ <script type="application/javascript">
+ /*
+ * Test for Bug 735031
+ * Test locking non-html element.
+ */
+ function begin() {
+ var elem = document.getElementById("svg-elem")
+ , elemWasLocked = false;
+
+ document.addEventListener("fullscreenchange", function (e) {
+ if (document.fullscreenElement === elem) {
+ elemWasLocked = true;
+ document.exitFullscreen();
+ } else {
+ opener.ok(elemWasLocked, "Expected SVG elem to become locked.");
+ opener.nextTest();
+ }
+ });
+ elem.requestFullscreen();
+ }
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-table.html b/dom/base/test/fullscreen/file_fullscreen-table.html
new file mode 100644
index 0000000000..39c602334a
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-table.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1223561</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<table style="background-color: green"></table>
+<script>
+"use strict";
+
+function ok(condition, msg) {
+ opener.ok(condition, "[table] " + msg);
+}
+
+function is(a, b, msg) {
+ opener.is(a, b, "[table] " + msg);
+}
+
+function info(msg) {
+ opener.info("[table] " + msg);
+}
+
+const gTable = document.querySelector("table");
+
+function begin() {
+ info("The default background of window should be white");
+ addFullscreenChangeContinuation("enter", enteredFullscreen);
+ assertWindowPureColor(window, "white");
+ gTable.requestFullscreen();
+}
+
+function enteredFullscreen() {
+ info("The table with green background should be in fullscreen");
+ assertWindowPureColor(window, "green");
+ gTable.style = "background: transparent";
+ info("When the table becames transparent, the black backdrop should appear");
+ assertWindowPureColor(window, "black");
+ addFullscreenChangeContinuation("exit", exitedFullscreen);
+ document.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ opener.nextTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-top-layer.html b/dom/base/test/fullscreen/file_fullscreen-top-layer.html
new file mode 100644
index 0000000000..9e95182b02
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-top-layer.html
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1126230</title>
+ <style>
+ #back {
+ position: fixed !important;
+ z-index: 2147483647 !important;
+ top: 0 !important; left: 0 !important;
+ right: 0 !important; bottom: 0 !important;
+ width: 100% !important; height: 100% !important;
+ }
+ #parent {
+ position: fixed;
+ z-index: -2147483748;
+ width: 0; height: 0;
+ overflow: hidden;
+ opacity: 0;
+ mask: url(#mask);
+ clip: rect(0, 0, 0, 0);
+ clip-path: url(#clipPath);
+ filter: opacity(0%);
+ will-change: transform;
+ perspective: 10px;
+ transform: scale(0);
+ }
+ /* The following styles are copied from ua.css to ensure that
+ * no other style change may trigger frame reconstruction */
+ :root {
+ overflow: hidden !important;
+ }
+ .two #fullscreen {
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ z-index: 2147483647 !important;
+ width: 100% !important;
+ height: 100% !important;
+ margin: 0 !important;
+ min-width: 0 !important;
+ max-width: none !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ box-sizing: border-box !important;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1126230">Mozilla Bug 1126230</a>
+<div id="parent">
+ <div id="fullscreen" style="background-color: green"></div>
+</div>
+<div id="back" style="background-color: red"></div>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <clipPath id="clipPath"></clipPath>
+ <mask id="mask"></mask>
+ </defs>
+</svg>
+<script>
+const gParentProperties = [
+ "position", "zIndex", "overflow",
+ "opacity", "mask", "clip", "clipPath",
+ "filter", "willChange", "transform"
+];
+
+var gInitialVals = {};
+
+const gParent = document.getElementById("parent");
+const gFullscreen = document.getElementById("fullscreen");
+const gBack = document.getElementById("back");
+
+function is(a, b, msg) {
+ opener.is(a, b, "[top-layer] " + msg);
+}
+
+function isnot(a, b, msg) {
+ opener.isnot(a, b, "[top-layer] " + msg);
+}
+
+function ok(cond, msg) {
+ opener.ok(cond, "[top-layer] " + msg);
+}
+
+function synthesizeMouseAtWindowCenter() {
+ synthesizeMouseAtPoint(innerWidth / 2, innerHeight / 2, {});
+}
+
+
+var tests = ["one", "two"];
+
+function begin() {
+ // record initial computed style of #parent
+ const style = getComputedStyle(gParent);
+ for (var prop of gParentProperties) {
+ gInitialVals[prop] = style[prop];
+ }
+
+ nextTest();
+}
+
+function nextTest() {
+ document.body.className = tests.shift();
+ // trigger a reflow to ensure the state of frames before fullscreen
+ gFullscreen.getBoundingClientRect();
+
+ ok(!document.fullscreenElement, "Shouldn't be in fullscreen");
+ // check window snapshot
+ assertWindowPureColor(window, "red");
+ // simulate click
+ window.addEventListener("click", firstClick);
+ synthesizeMouseAtWindowCenter();
+}
+
+function firstClick(evt) {
+ window.removeEventListener("click", firstClick);
+ is(evt.target, gBack, "Click target should be #back before fullscreen");
+ addFullscreenChangeContinuation("enter", enterFullscreen);
+ gFullscreen.requestFullscreen();
+}
+
+function enterFullscreen() {
+ ok(document.fullscreenElement, "Should now be in fullscreen");
+ // check window snapshot
+ assertWindowPureColor(window, "green");
+ // check computed style of #parent
+ const style = getComputedStyle(gParent);
+ for (var prop of gParentProperties) {
+ is(style[prop], gInitialVals[prop],
+ `Computed style ${prop} of #parent should not be changed`);
+ }
+ // simulate click
+ window.addEventListener("click", secondClick);
+ synthesizeMouseAtWindowCenter();
+}
+
+function secondClick(evt) {
+ window.removeEventListener("click", secondClick);
+ is(evt.target, gFullscreen, "Click target should be #fullscreen now");
+ addFullscreenChangeContinuation("exit", exitFullscreen);
+ document.exitFullscreen();
+}
+
+function exitFullscreen() {
+ if (tests.length) {
+ nextTest();
+ } else {
+ opener.nextTest();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen-utils.js b/dom/base/test/fullscreen/file_fullscreen-utils.js
new file mode 100644
index 0000000000..b4779da4de
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-utils.js
@@ -0,0 +1,87 @@
+// Keep track of how many fullscreenChange enters we've received, so that
+// we can balance them with the number of exits we receive. We reset this
+// to 0 when we load a test.
+var fullscreenChangeEnters = 0;
+
+addLoadEvent(function () {
+ info(`Resetting fullscreen enter count.`);
+ fullscreenChangeEnters = 0;
+});
+
+// This can be used to force a certain value for fullscreenChangeEnters
+// to handle unusual conditions -- such as exiting multiple levels of
+// fullscreen forcibly.
+function setFullscreenChangeEnters(enters) {
+ info(`Setting fullscreen enter count to ${enters}.`);
+ fullscreenChangeEnters = enters;
+}
+
+// Returns true if the window believes it is in fullscreen. This may be true even
+// before an asynchronous fullscreen transition is complete.
+function inFullscreenMode(win) {
+ return win.document.fullscreenElement;
+}
+
+// Adds a listener that will be called once a fullscreen transition
+// is complete. When type==='enter', callback is called when we've
+// received a fullscreenchange event, and the fullscreen transition is
+// complete. When type==='exit', callback is called when we've
+// received a fullscreenchange event and the window is out of
+// fullscreen. inDoc is the document which the listeners are added on,
+// if absent, the listeners are added to the current document.
+// the current document.
+function addFullscreenChangeContinuation(type, callback, inDoc) {
+ var doc = inDoc || document;
+ var topWin = doc.defaultView.top;
+ function checkCondition() {
+ if (type == "enter") {
+ fullscreenChangeEnters++;
+ return inFullscreenMode(topWin);
+ } else if (type == "exit") {
+ fullscreenChangeEnters--;
+ return fullscreenChangeEnters
+ ? inFullscreenMode(topWin)
+ : !inFullscreenMode(topWin);
+ }
+ throw new Error("'type' must be either 'enter', or 'exit'.");
+ }
+ function onFullscreenChange(event) {
+ doc.removeEventListener("fullscreenchange", onFullscreenChange);
+ ok(checkCondition(), `Should ${type} fullscreen.`);
+ // Delay invocation so other listeners have a chance to respond before
+ // we continue.
+ requestAnimationFrame(() => setTimeout(() => callback(event), 0), 0);
+ }
+ doc.addEventListener("fullscreenchange", onFullscreenChange);
+}
+
+// Calls |callback| when the next fullscreenerror is dispatched to inDoc||document.
+function addFullscreenErrorContinuation(callback, inDoc) {
+ let doc = inDoc || document;
+ let listener = function (event) {
+ doc.removeEventListener("fullscreenerror", listener);
+ // Delay invocation so other listeners have a chance to respond before
+ // we continue.
+ requestAnimationFrame(() => setTimeout(() => callback(event), 0), 0);
+ };
+ doc.addEventListener("fullscreenerror", listener);
+}
+
+// Waits until the window has both the load event and a MozAfterPaint called on
+// it, and then invokes the callback
+function waitForLoadAndPaint(win, callback) {
+ win.addEventListener(
+ "MozAfterPaint",
+ function () {
+ // The load event may have fired before the MozAfterPaint, in which case
+ // listening for it now will hang. Instead we check the readyState to see if
+ // it already fired, and if so, invoke the callback right away.
+ if (win.document.readyState == "complete") {
+ callback();
+ } else {
+ win.addEventListener("load", callback, { once: true });
+ }
+ },
+ { once: true }
+ );
+}
diff --git a/dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html b/dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html
new file mode 100644
index 0000000000..620bc5acf9
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen-with-full-zoom.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1223561</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" src="file_fullscreen-utils.js"></script>
+</head>
+<body>
+<div id="target" style="width: 100px; height: 100px; background-color: green;"></div>
+<script>
+"use strict";
+
+function begin() {
+ info("Setting full zoom to 30%");
+ SpecialPowers.setFullZoom(window, 0.3);
+
+ addFullscreenChangeContinuation("enter", enteredFullscreen);
+ document.getElementById("target").requestFullscreen();
+}
+
+function enteredFullscreen() {
+ info("The element with green background should be in fullscreen");
+ assertWindowPureColor(window, "green");
+ addFullscreenChangeContinuation("exit", exitedFullscreen);
+ document.exitFullscreen();
+}
+
+function exitedFullscreen() {
+ opener.nextTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/file_fullscreen_meta_viewport.html b/dom/base/test/fullscreen/file_fullscreen_meta_viewport.html
new file mode 100644
index 0000000000..9938fdda6b
--- /dev/null
+++ b/dom/base/test/fullscreen/file_fullscreen_meta_viewport.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta name=viewport content="width=980">
+<style>
+ #player {
+ background: green;
+ }
+ #overflow {
+ height: 500vh;
+ }
+</style>
+<div id="player"></div>
+<div id="overflow"></div>
diff --git a/dom/base/test/fullscreen/fullscreen.xhtml b/dom/base/test/fullscreen/fullscreen.xhtml
new file mode 100644
index 0000000000..2cc95642b6
--- /dev/null
+++ b/dom/base/test/fullscreen/fullscreen.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test for fullscreen sizemode in chrome
+ -->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ sizemode="fullscreen">
+
+<script>
+
+window.addEventListener("fullscreen", onFullScreen, true);
+
+function onFullScreen(event)
+{
+ window.arguments[0].done(window.fullScreen);
+}
+
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+
+<button id="find-button" label="Find"/>
+<button id="cancel-button" label="Cancel"/>
+
+</body>
+</window>
diff --git a/dom/base/test/fullscreen/fullscreen_helpers.js b/dom/base/test/fullscreen/fullscreen_helpers.js
new file mode 100644
index 0000000000..6e78015cd8
--- /dev/null
+++ b/dom/base/test/fullscreen/fullscreen_helpers.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URLS = [
+ // all frames are in different process.
+ `data:text/html,
+ <div name="div" id="div" style="width: 100px; height: 100px; background: red;">
+ <iframe id="iframe" allowfullscreen="yes"
+ src="http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-middle.html"></iframe>
+ </div>`,
+ // toplevel and inner most iframe are in same process, and middle iframe is
+ // in a different process.
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ `http://example.org/browser/dom/base/test/fullscreen/file_fullscreen-iframe-top.html`,
+ // toplevel and middle iframe are in same process, and inner most iframe is
+ // in a different process.
+ `http://mochi.test:8888/browser/dom/base/test/fullscreen/file_fullscreen-iframe-top.html`,
+];
+
+function waitRemoteFullscreenExitEvents(aBrowsingContexts) {
+ let promises = [];
+ aBrowsingContexts.forEach(([aBrowsingContext, aName]) => {
+ promises.push(
+ SpecialPowers.spawn(aBrowsingContext, [aName], async name => {
+ return new Promise(resolve => {
+ let document = content.document;
+ document.addEventListener(
+ "fullscreenchange",
+ function changeHandler() {
+ if (document.fullscreenElement) {
+ return;
+ }
+
+ ok(true, `check remote DOM fullscreen event (${name})`);
+ document.removeEventListener("fullscreenchange", changeHandler);
+ resolve();
+ }
+ );
+ });
+ })
+ );
+ });
+ return Promise.all(promises);
+}
+
+function waitDOMFullscreenEvent(
+ aDocument,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return new Promise(resolve => {
+ function errorHandler() {
+ ok(false, "should not get fullscreenerror event");
+ aDocument.removeEventListener("fullscreenchange", changeHandler);
+ aDocument.removeEventListener("fullscreenerror", errorHandler);
+ resolve();
+ }
+
+ function changeHandler() {
+ if (aWaitUntil && aIsInFullscreen != !!aDocument.fullscreenElement) {
+ return;
+ }
+
+ is(
+ aIsInFullscreen,
+ !!aDocument.fullscreenElement,
+ "check DOM fullscreen (event)"
+ );
+ aDocument.removeEventListener("fullscreenchange", changeHandler);
+ aDocument.removeEventListener("fullscreenerror", errorHandler);
+ resolve();
+ }
+
+ aDocument.addEventListener("fullscreenchange", changeHandler);
+ aDocument.addEventListener("fullscreenerror", errorHandler);
+ });
+}
+
+function waitWidgetFullscreenEvent(
+ aWindow,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return BrowserTestUtils.waitForEvent(aWindow, "fullscreen", false, aEvent => {
+ if (
+ aWaitUntil &&
+ aIsInFullscreen !=
+ aWindow.document.documentElement.hasAttribute("inFullscreen")
+ ) {
+ return false;
+ }
+
+ is(
+ aIsInFullscreen,
+ aWindow.document.documentElement.hasAttribute("inFullscreen"),
+ "check widget fullscreen (event)"
+ );
+ return true;
+ });
+}
+
+function waitForFullScreenObserver(
+ aDocument,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return TestUtils.topicObserved("fullscreen-painted", (subject, data) => {
+ if (
+ aWaitUntil &&
+ aIsInFullscreen !=
+ aDocument.documentElement.hasAttribute("inDOMFullscreen")
+ ) {
+ return false;
+ }
+
+ is(
+ aIsInFullscreen,
+ aDocument.documentElement.hasAttribute("inDOMFullscreen"),
+ "check fullscreen (observer)"
+ );
+ return true;
+ });
+}
+
+function waitForFullscreenState(
+ aDocument,
+ aIsInFullscreen,
+ aWaitUntil = false
+) {
+ return Promise.all([
+ waitWidgetFullscreenEvent(
+ aDocument.defaultView,
+ aIsInFullscreen,
+ aWaitUntil
+ ),
+ waitDOMFullscreenEvent(aDocument, aIsInFullscreen, aWaitUntil),
+ waitForFullScreenObserver(aDocument, aIsInFullscreen, aWaitUntil),
+ ]);
+}
+
+// Wait for fullscreenchange event for fullscreen exit. And wait for
+// fullscreen-painted observed conditionally.
+async function waitForFullscreenExit(aDocument) {
+ info(`waitForFullscreenExit`);
+ let promiseFsObserver = null;
+ let observer = function () {
+ if (aDocument.documentElement.hasAttribute("inDOMFullscreen")) {
+ info(`waitForFullscreenExit, fullscreen-painted, inDOMFullscreen`);
+ Services.obs.removeObserver(observer, "fullscreen-painted");
+ promiseFsObserver = waitForFullScreenObserver(aDocument, false);
+ }
+ };
+ Services.obs.addObserver(observer, "fullscreen-painted");
+
+ await waitDOMFullscreenEvent(aDocument, false, true);
+ // If there is a fullscreen-painted observer notified for inDOMFullscreen set,
+ // we expect to have a subsequent fullscreen-painted observer notified with
+ // inDOMFullscreen unset.
+ if (promiseFsObserver) {
+ info(`waitForFullscreenExit, promiseFsObserver`);
+ await promiseFsObserver;
+ return;
+ }
+
+ Services.obs.removeObserver(observer, "fullscreen-painted");
+ // If inDOMFullscreen is set we expect to have a subsequent fullscreen-painted
+ // observer notified with inDOMFullscreen unset.
+ if (aDocument.documentElement.hasAttribute("inDOMFullscreen")) {
+ info(`waitForFullscreenExit, inDOMFullscreen`);
+ await waitForFullScreenObserver(aDocument, false, true);
+ }
+}
diff --git a/dom/base/test/fullscreen/head.js b/dom/base/test/fullscreen/head.js
new file mode 100644
index 0000000000..1e3b435d0c
--- /dev/null
+++ b/dom/base/test/fullscreen/head.js
@@ -0,0 +1,65 @@
+function pushPrefs(...aPrefs) {
+ return SpecialPowers.pushPrefEnv({ set: aPrefs });
+}
+
+function promiseWaitForEvent(
+ object,
+ eventName,
+ capturing = false,
+ chrome = false
+) {
+ return new Promise(resolve => {
+ function listener(event) {
+ info("Saw " + eventName);
+ object.removeEventListener(eventName, listener, capturing, chrome);
+ resolve(event);
+ }
+
+ info("Waiting for " + eventName);
+ object.addEventListener(eventName, listener, capturing, chrome);
+ });
+}
+
+/**
+ * Waits for the next load to complete in any browser or the given browser.
+ * If a <tabbrowser> is given it waits for a load in any of its browsers.
+ *
+ * @return promise
+ */
+function waitForDocLoadComplete(aBrowser = gBrowser) {
+ return new Promise(resolve => {
+ let listener = {
+ onStateChange(webProgress, req, flags, status) {
+ let docStop =
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+ Ci.nsIWebProgressListener.STATE_STOP;
+ info(
+ "Saw state " +
+ flags.toString(16) +
+ " and status " +
+ status.toString(16)
+ );
+ // When a load needs to be retargetted to a new process it is cancelled
+ // with NS_BINDING_ABORTED so ignore that case
+ if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
+ aBrowser.removeProgressListener(this);
+ waitForDocLoadComplete.listeners.delete(this);
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ info("Browser loaded " + chan.originalURI.spec);
+ resolve();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ aBrowser.addProgressListener(listener);
+ waitForDocLoadComplete.listeners.add(listener);
+ info("Waiting for browser load");
+ });
+}
+// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
+// they're not GC'ed before we saw the page load.
+waitForDocLoadComplete.listeners = new Set();
+registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
diff --git a/dom/base/test/fullscreen/mochitest.toml b/dom/base/test/fullscreen/mochitest.toml
new file mode 100644
index 0000000000..835efffa97
--- /dev/null
+++ b/dom/base/test/fullscreen/mochitest.toml
@@ -0,0 +1,64 @@
+[DEFAULT]
+tags = "fullscreen"
+support-files = [
+ "file_fullscreen-api-race.html",
+ "file_fullscreen-api.html",
+ "file_fullscreen-async.html",
+ "file_fullscreen-backdrop.html",
+ "file_fullscreen-denied-inner.html",
+ "file_fullscreen-denied.html",
+ "file_fullscreen-esc-exit-inner.html",
+ "file_fullscreen-esc-exit.html",
+ "file_fullscreen-event-order.html",
+ "file_fullscreen-featurePolicy.html",
+ "file_fullscreen-featurePolicy-inner.html",
+ "file_fullscreen-focus.html",
+ "file_fullscreen-focus-inner.html",
+ "file_fullscreen-hidden.html",
+ "file_fullscreen-lenient-setters.html",
+ "file_fullscreen_meta_viewport.html",
+ "file_fullscreen-multiple-inner.html",
+ "file_fullscreen-multiple.html",
+ "file_fullscreen-navigation.html",
+ "file_fullscreen-nested.html",
+ "file_fullscreen-prefixed.html",
+ "file_fullscreen-resize.html",
+ "file_fullscreen-rollback.html",
+ "file_fullscreen-scrollbar.html",
+ "file_fullscreen-selector.html",
+ "file_fullscreen-shadowdom.html",
+ "file_fullscreen-single.html",
+ "file_fullscreen-sub-iframe.html",
+ "file_fullscreen-svg-element.html",
+ "file_fullscreen-table.html",
+ "file_fullscreen-top-layer.html",
+ "file_fullscreen-utils.js",
+ "file_fullscreen-with-full-zoom.html",
+]
+
+["test_fullscreen-api-race.html"]
+skip-if = [
+ "os == 'android'", # same as test_fullscreen-api.html, 1356570
+ "os == 'mac' && debug",
+]
+
+["test_fullscreen-api-rapid-cycle.html"]
+
+["test_fullscreen-api.html"]
+allow_xul_xbl = true # XUL is used in file_fullscreen-api.html
+skip-if = [
+ "os == 'android'",
+ "os == 'mac'", # Bug 1579623, 1776996
+ "display == 'wayland' && os_version == '22.04'", # Bug 1857240
+ "http3",
+ "http2",
+]
+
+["test_fullscreen_meta_viewport.html"]
+
+["test_fullscreen_modal.html"]
+skip-if = [
+ "display == 'wayland' && os_version == '22.04'", # Bug 1857240
+ "http3",
+ "http2",
+]
diff --git a/dom/base/test/fullscreen/moz.build b/dom/base/test/fullscreen/moz.build
new file mode 100644
index 0000000000..3ac1d85dff
--- /dev/null
+++ b/dom/base/test/fullscreen/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.toml",
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ "chrome.toml",
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser.toml",
+]
diff --git a/dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml b/dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml
new file mode 100644
index 0000000000..3041d851ac
--- /dev/null
+++ b/dom/base/test/fullscreen/test_MozDomFullscreen_event.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test that "MozShowFullScreenWarning" is dispatched to chrome on restricted keypress.
+ -->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" width="400" height="400">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+// Ensure the full-screen api is enabled, and will be disabled on test exit.
+var gPrevEnabled = SpecialPowers.getBoolPref("full-screen-api.enabled");
+var gPrevTrusted = SpecialPowers.getBoolPref("full-screen-api.allow-trusted-requests-only");
+var newwindow;
+
+// Ensure "fullscreen" permissions are not present on the test URI.
+var uri = Services.io.newURI("http://mochi.test:8888");
+var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
+Services.perms.removeFromPrincipal(principal, "fullscreen");
+
+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']
+]}).then(setup);
+
+function setup() {
+ newwindow = window.browsingContext.topChromeWindow.openDialog(
+ "MozDomFullscreen_chrome.xhtml", "_blank","chrome,dialog=no,resizable=yes,width=400,height=400", window);
+}
+
+function done()
+{
+ newwindow.close();
+ SimpleTest.finish();
+}
+
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+</window>
diff --git a/dom/base/test/fullscreen/test_fullscreen-api-race.html b/dom/base/test/fullscreen/test_fullscreen-api-race.html
new file mode 100644
index 0000000000..3fe4cd3500
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen-api-race.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for race conditions of Fullscreen API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+
+function Deferred() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+}
+
+function checkIsChromeFullscreen(win, inFullscreen) {
+ return SimpleTest.promiseWaitForCondition(
+ () => win.fullScreen == inFullscreen,
+ "The window should exit fullscreen state");
+}
+
+SimpleTest.waitForExplicitFinish();
+// XXX This actually exposes a true race condition, but it could rarely
+// happen in real world, because it only happens when requestFullscreen
+// is called immediately after exiting fullscreen in certain condition,
+// and in real life, requestFullscreen can only be called inside a user
+// event handler. But we want to fix this race condition at some point,
+// via queuing all exiting request as well as entering request together
+// which we may eventually need to do for bug 1188256.
+SimpleTest.requestFlakyTimeout(
+ "Need to wait for potential fullscreen transition");
+addLoadEvent(function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ]
+ }, next);
+});
+
+const OPEN_WINDOW_FUNCS = [
+ function openNewTab() {
+ return new Promise(resolve => {
+ var win = window.open("about:blank");
+ win.addEventListener("load", () => {
+ resolve(win);
+ }, { once: true });
+ });
+ },
+ function openNewWindow() {
+ return new Promise(resolve => {
+ var win = window.open("about:blank", "", "width=300,height=200");
+ win.addEventListener("load", () => {
+ resolve(win);
+ }, { once: true });
+ });
+ }
+];
+
+const ACTION_FUNCS = [
+ function navigate(win) {
+ info("About to navigate to another page");
+ var promise = new Promise(resolve => {
+ window.addEventListener("message", () => {
+ SimpleTest.waitForFocus(() => {
+ checkIsChromeFullscreen(win, false).then(() => {
+ win.close();
+ resolve();
+ });
+ }, win);
+ }, { once: true });
+ });
+ win.location = "file_fullscreen-api-race.html";
+ return promise;
+ },
+ function closeWindow(win) {
+ info("About to close the window");
+ win.close();
+ return Promise.resolve();
+ },
+ function exitFullscreen(win) {
+ info("About to cancel fullscreen");
+ var deferred = new Deferred();
+ function listener() {
+ win.removeEventListener("fullscreenchange", listener);
+ ok(!win.document.fullscreenElement, "Should exit fullscreen");
+ checkIsChromeFullscreen(win, false).then(() => {
+ win.close();
+ deferred.resolve();
+ });
+ }
+ win.addEventListener("fullscreenchange", listener);
+ win.document.exitFullscreen();
+ return deferred.promise;
+ },
+ function exitAndClose(win) {
+ info("About to cancel fullscreen and close the window");
+ win.document.exitFullscreen();
+ win.close();
+ return Promise.resolve();
+ }
+];
+
+function* testGenerator() {
+ for (var openWinFunc of OPEN_WINDOW_FUNCS) {
+ for (var actionFunc of ACTION_FUNCS) {
+ info(`Testing ${openWinFunc.name}, ${actionFunc.name}`);
+ yield { openWinFunc, actionFunc };
+ }
+ }
+}
+
+function runTest(test) {
+ var winPromise = test.openWinFunc();
+ return winPromise.then((win) => {
+ return new Promise(resolve => {
+ SimpleTest.waitForFocus(() => resolve(win), win, true);
+ });
+ }).then((win) => {
+ return new Promise((resolve, reject) => {
+ var retried = false;
+ function listener(evt) {
+ if (!retried && evt.type == "fullscreenerror") {
+ todo(false, "Failed to enter fullscreen, but try again");
+ retried = true;
+ SimpleTest.waitForFocus(() => {
+ win.document.documentElement.requestFullscreen();
+ }, win, true);
+ return;
+ }
+ win.removeEventListener("fullscreenchange", listener);
+ win.removeEventListener("fullscreenerror", listener);
+ is(evt.type, "fullscreenchange", "Should get fullscreenchange");
+ ok(win.document.fullscreenElement, "Should have entered fullscreen");
+ ok(win.fullScreen, "The window should be in fullscreen");
+ test.actionFunc(win).then(() => resolve(win));
+ }
+ if (win.fullScreen) {
+ todo(false, "Should not open in fullscreen mode");
+ win.close();
+ reject();
+ return;
+ }
+ info("About to enter fullscreen");
+ win.addEventListener("fullscreenchange", listener);
+ win.addEventListener("fullscreenerror", listener);
+ win.document.documentElement.requestFullscreen();
+ });
+ }).then((win) => {
+ ok(win.closed, "The window should have been closed");
+ });
+}
+
+var tests = testGenerator();
+
+function next() {
+ var test = tests.next().value;
+ if (test) {
+ runTest(test).catch(() => {
+ return new Promise(resolve => {
+ SimpleTest.waitForFocus(resolve);
+ }).then(() => runTest(test));
+ }).catch(() => {
+ ok(false, "Fail to run test " +
+ `${test.openWinFunc.name}, ${test.actionFunc.name}`);
+ }).then(() => {
+ setTimeout(() => SimpleTest.waitForFocus(next), 1000);
+ });
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html b/dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html
new file mode 100644
index 0000000000..36e622ad4c
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen-api-rapid-cycle.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for rapid cycling of Fullscreen API requests</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+
+// There are two ways that web content should be able to reliably
+// request and respond to fullscreen:
+//
+// 1) Wait on the requestFullscreen() and exitFullscreen() promises.
+// 2) Respond to the "fullscreenchange" and "fullscreenerror" events
+// after calling requestFullscreen() or exitFullscreen().
+//
+// This test exercises both methods rapidly, while checking to see
+// if any expected signal is taking too long. If awaiting a promise
+// or waiting for an event takes longer than some number of seconds,
+// the test will fail instead of timing out. This is to help detect
+// vulnerabilities in the implementation which would slow down the
+// test harness waiting for the timeout.
+
+// How many enter-exit cycles we run for each method of detecting a
+// fullscreen transition.
+const CYCLE_COUNT = 3;
+
+// How long do we wait for one transition before considering it as
+// an error.
+const TOO_LONG_SECONDS = 3;
+
+SimpleTest.requestFlakyTimeout("We race against Promises to turn possible timeouts into errors.");
+
+function rejectAfterTooLong() {
+ return new Promise((resolve, reject) => {
+ const fail = () => {
+ reject(`timeout after ${TOO_LONG_SECONDS} seconds`);
+ }
+ setTimeout(fail, TOO_LONG_SECONDS * 1000);
+ });
+}
+
+add_setup(async () => {
+ await SpecialPowers.pushPrefEnv({
+ "set": [
+ // Keep the test structure simple.
+ ["full-screen-api.allow-trusted-requests-only", false],
+
+ // Make macOS fullscreen transitions asynchronous.
+ ["full-screen-api.macos-native-full-screen", true],
+
+ // Clarify that even no-duration async transitions are vulnerable.
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ ]
+ });
+});
+
+add_task(ensureOutOfFullscreen);
+
+// It is an implementation detail that promises resolve first, and
+// then events are fired on a later event loop. For this reason,
+// it's very important that we do the rapidCycleAwaitEvents task
+// first, because we don't want to have any "stray" fullscreenchange
+// events in the pipeline when we start that task. Conversely,
+// there's really no way for the rapidCycleAwaitEvents to poison
+// the environment for the next task, which waits on promises.
+add_task(rapidCycleAwaitEvents);
+
+add_task(ensureOutOfFullscreen);
+
+add_task(rapidCycleAwaitPromises);
+
+add_task(() => { ok(true, "Completed test with one expected result."); });
+
+// This is a helper function to repeatedly invoke a Promise generator
+// until the Promise resolves, delaying by one event loop on each
+// attempt.
+async function repeatUntilSuccessful(f) {
+ let successful = false;
+ do {
+ try {
+ // Delay one event loop.
+ await new Promise(r => SimpleTest.executeSoon(r));
+ await f();
+ successful = true;
+ } catch (error) {
+ info(`repeatUntilSuccessful: error ${error}.`);
+ }
+ } while(!successful);
+}
+
+async function ensureOutOfFullscreen() {
+ // Repeatedly call exitFullscreen until we get out.
+ await repeatUntilSuccessful(async () => {
+ if (document.fullscreenElement) {
+ await document.exitFullscreen();
+ }
+ if (document.fullscreenElement) {
+ throw new Error("still in fullscreen");
+ }
+ });
+}
+
+async function rapidCycleAwaitEvents() {
+ const receiveOneFullscreenchange = () => {
+ return new Promise(resolve => {
+ document.addEventListener("fullscreenchange", resolve, { once: true });
+ });
+ };
+
+ let gotError = false;
+ for (let cycle = 0; cycle < CYCLE_COUNT; cycle++) {
+ info(`Event cycle ${cycle} request fullscreen.`);
+ const enterPromise = receiveOneFullscreenchange();
+ document.documentElement.requestFullscreen();
+ await Promise.race([enterPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Event cycle ${cycle} requestFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+
+ info(`Event cycle ${cycle} exit fullscreen.`);
+ const exitPromise = receiveOneFullscreenchange();
+ document.exitFullscreen();
+ await Promise.race([exitPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Event cycle ${cycle} exitFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+ }
+}
+
+async function rapidCycleAwaitPromises() {
+ let gotError = false;
+ for (let cycle = 0; cycle < CYCLE_COUNT; cycle++) {
+ info(`Promise cycle ${cycle} request fullscreen.`);
+ const enterPromise = document.documentElement.requestFullscreen();
+ await Promise.race([enterPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Promise cycle ${cycle} requestFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+
+ info(`Promise cycle ${cycle} exit fullscreen.`);
+ const exitPromise = document.exitFullscreen();
+ await Promise.race([exitPromise, rejectAfterTooLong()]).catch(error => {
+ ok(false, `Promise cycle ${cycle} exitFullscreen errored with ${error}.`);
+ gotError = true;
+ });
+ if (gotError) {
+ break;
+ }
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/test_fullscreen-api.html b/dom/base/test/fullscreen/test_fullscreen-api.html
new file mode 100644
index 0000000000..2a59d6eeb0
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen-api.html
@@ -0,0 +1,150 @@
+ <!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 545812</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="file_fullscreen-utils.js"></script>
+ <style>
+ body {
+ background-color: black;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=545812">Mozilla Bug 545812</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Tests for Bug 545812 **/
+SimpleTest.requestFlakyTimeout("untriaged");
+
+// Run the tests which go full-screen in new windows, as mochitests normally
+// run in an iframe, which by default will not have the allowfullscreen
+// attribute set, so full-screen won't work.
+var gTestWindows = [
+ { test: "file_fullscreen-single.html" },
+ { test: "file_fullscreen-multiple.html",
+ prefs: [["full-screen-api.exit-on.windowRaise", false],
+ ["full-screen-api.exit-on.windowOpen", false]] },
+ { test: "file_fullscreen-rollback.html" },
+ { test: "file_fullscreen-esc-exit.html" },
+ { test: "file_fullscreen-denied.html" },
+ { test: "file_fullscreen-api.html" },
+ { test: "file_fullscreen-hidden.html" },
+ { test: "file_fullscreen-focus.html" },
+ { test: "file_fullscreen-svg-element.html" },
+ { test: "file_fullscreen-navigation.html" },
+ { test: "file_fullscreen-scrollbar.html" },
+ { test: "file_fullscreen-selector.html" },
+ { test: "file_fullscreen-shadowdom.html" },
+ { test: "file_fullscreen-top-layer.html" },
+ { test: "file_fullscreen-backdrop.html" },
+ { test: "file_fullscreen-nested.html" },
+ { test: "file_fullscreen-prefixed.html" },
+ { test: "file_fullscreen-lenient-setters.html" },
+ { test: "file_fullscreen-table.html" },
+ { test: "file_fullscreen-event-order.html" },
+ { test: "file_fullscreen-featurePolicy.html",
+ prefs: [["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true]] },
+ { test: "file_fullscreen-async.html" },
+ { test: "file_fullscreen-sub-iframe.html" },
+ { test: "file_fullscreen-with-full-zoom.html" },
+ { test: "file_fullscreen-resize.html" },
+];
+
+var testWindow = null;
+var gTestIndex = 0;
+
+function finish() {
+ SimpleTest.finish();
+}
+
+function nextTest() {
+ if (testWindow) {
+ info("Waiting for focus to return to main window");
+ window.addEventListener("focus", function() {
+ info("main window focused, starting next test");
+ SimpleTest.executeSoon(runNextTest);
+ }, {once: true});
+ info("testWindow.close()");
+ testWindow.close();
+ } else {
+ SimpleTest.executeSoon(runNextTest);
+ }
+}
+
+function waitForEvent(eventTarget, eventName, checkFn, callback) {
+ eventTarget.addEventListener(eventName, function listener(event) {
+ if (checkFn && !checkFn(event)) {
+ return;
+ }
+ eventTarget.removeEventListener(eventName, listener);
+ callback();
+ });
+}
+
+function runNextTest() {
+ if (gTestIndex < gTestWindows.length) {
+ let test = gTestWindows[gTestIndex];
+ let promise = ("prefs" in test)
+ ? SpecialPowers.pushPrefEnv({"set": test.prefs})
+ : Promise.resolve();
+ promise.then(function() {
+ info(`Run test ${test.test}`);
+ testWindow = window.open(test.test, "", "width=500,height=500,scrollbars=yes");
+ // We'll wait for the window to load, then make sure our window is refocused
+ // before starting the test, which will get kicked off on "focus".
+ // This ensures that we're essentially back on the primary "desktop" on
+ // OS X Lion before we run the test.
+ waitForLoadAndPaint(testWindow, function() {
+ SimpleTest.waitForFocus(function() {
+ info("Were focused");
+ // For the platforms that support reporting occlusion state (e.g. Mac),
+ // we should wait until the docshell has been activated again,
+ // otherwise, the fullscreen request might be denied.
+ if (testWindow.document.hidden) {
+ info("Waiting for document to unhide");
+ waitForEvent(testWindow.document, "visibilitychange", (event) => {
+ return !testWindow.document.hidden;
+ }, testWindow.begin);
+ return;
+ }
+ testWindow.begin();
+ }, testWindow);
+ });
+ });
+ gTestIndex++;
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+try {
+ window.fullScreen = true;
+} catch (e) {
+}
+is(window.fullScreen, false, "Shouldn't be able to set window fullscreen from content");
+// Ensure the full-screen api is enabled, and will be disabled on test exit.
+// Disable the requirement for trusted contexts only, so the tests are easier
+// to write
+addLoadEvent(function() {
+ 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"]
+ ]}, nextTest);
+});
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/fullscreen/test_fullscreen.xhtml b/dom/base/test/fullscreen/test_fullscreen.xhtml
new file mode 100644
index 0000000000..e338bac523
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ Test for fullscreen sizemode in chrome
+ -->
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ sizemode="fullscreen">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+let newwindow = window.browsingContext.topChromeWindow.openDialog("fullscreen.xhtml", "_blank","chrome,resizable=yes", window);
+
+function done()
+{
+ // because we are cancelling the fullscreen event, it
+ // takes a bit for the fullScreen property to be set
+ setTimeout(function() { this.complete(); }, 0);
+}
+
+function complete()
+{
+ ok(newwindow.fullScreen, "window.fullScreen is true.");
+ newwindow.close();
+ SimpleTest.finish();
+}
+
+</script>
+
+
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+</window>
diff --git a/dom/base/test/fullscreen/test_fullscreen_meta_viewport.html b/dom/base/test/fullscreen/test_fullscreen_meta_viewport.html
new file mode 100644
index 0000000000..c2cd355c6b
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen_meta_viewport.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Test for Bug 545812</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.meta-viewport.enabled", true]]
+ });
+
+ let win = window.open("file_fullscreen_meta_viewport.html", "", "width=500,height=500,scrollbars=yes");
+ await SimpleTest.promiseFocus(win);
+
+ is(win.innerWidth, 980, "Meta viewport should be in effect");
+
+ let element = win.document.querySelector("#player");
+ await SpecialPowers.wrap(element).requestFullscreen();
+
+ ok(win.document.fullscreen, "Window should be in fullscreen");
+ is(win.document.fullscreenElement, element, "#player should be the fullscreen element");
+ is(win.innerWidth, screen.width, "Should be fullscreen (w)");
+ is(win.innerHeight, screen.height, "Should be fullscreen (h)");
+ is(element.clientWidth, win.innerWidth, "Element should fill the viewport vertically");
+ is(element.clientHeight, win.innerHeight, "Element should fill the viewport vertically");
+
+ SpecialPowers.wrap(win.document).exitFullscreen();
+ win.close();
+ SimpleTest.finish();
+}())
+</script>
diff --git a/dom/base/test/fullscreen/test_fullscreen_modal.html b/dom/base/test/fullscreen/test_fullscreen_modal.html
new file mode 100644
index 0000000000..78e70d9052
--- /dev/null
+++ b/dom/base/test/fullscreen/test_fullscreen_modal.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<title>Test for bug 1771150</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<style>
+ #fullscreen {
+ background-color: rgba(0, 255, 0, .5);
+ }
+ #fullscreen::backdrop {
+ background-color: transparent;
+ }
+ #fullscreen, #fullscreen::backdrop {
+ pointer-events: none;
+ }
+</style>
+<div id="fullscreen"></div>
+<button>Go fullscreen</button>
+<script>
+const button = document.querySelector("button");
+let clickCount = 0;
+let lastFullscreenPromise = null;
+let shouldEnterFullscreen = false;
+button.addEventListener("click", function(e) {
+ clickCount++;
+ if (shouldEnterFullscreen) {
+ const fullscreenElement = document.getElementById("fullscreen");
+ lastFullscreenPromise = SimpleTest.promiseFocus().then(() => {
+ fullscreenElement.focus();
+ return fullscreenElement.requestFullscreen();
+ });
+ }
+});
+
+function clickButton(expectEvent) {
+ let lastClickCount = clickCount;
+ synthesizeMouseAtCenter(button, {});
+ (expectEvent ? isnot : is)(clickCount, lastClickCount, `Should've ${expectEvent ? "" : "not "}been able to click`);
+}
+
+function enterFullscreen() {
+ lastFullscreenPromise = null;
+ shouldEnterFullscreen = true;
+ clickButton(true);
+ shouldEnterFullscreen = false;
+ isnot(lastFullscreenPromise, null, "Should be transitioning to fullscreen");
+ return lastFullscreenPromise;
+}
+
+async function testFullscreenIsModal(modal) {
+ info("testing modal: " + modal);
+ is(document.fullscreenElement, null, "Shouldn't be in fullscreen");
+
+ await enterFullscreen();
+
+ clickButton(/* expectEvent = */ !modal);
+
+ ok(document.fullscreenElement.matches(":fullscreen"), "Fullscreen element matches :fullscreen");
+ is(document.fullscreenElement.matches(":modal"), modal, "Fullscreen element matches :modal");
+
+ await document.exitFullscreen();
+ clickButton(/* expectEvent = */ true);
+}
+
+add_task(async function() {
+ await testFullscreenIsModal(true);
+});
+
+</script>