diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/base/test/fullscreen | |
parent | Initial commit. (diff) | |
download | firefox-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 'dom/base/test/fullscreen')
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> |