diff options
Diffstat (limited to '')
21 files changed, 2100 insertions, 0 deletions
diff --git a/browser/base/content/test/fullscreen/FullscreenFrame.sys.mjs b/browser/base/content/test/fullscreen/FullscreenFrame.sys.mjs new file mode 100644 index 0000000000..9821837b3f --- /dev/null +++ b/browser/base/content/test/fullscreen/FullscreenFrame.sys.mjs @@ -0,0 +1,103 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * test helper JSWindowActors used by the browser_fullscreen_api_fission.js test. + */ + +export class FullscreenFrameChild extends JSWindowActorChild { + actorCreated() { + this.fullscreen_events = []; + } + + changed() { + return new Promise(resolve => { + this.contentWindow.document.addEventListener( + "fullscreenchange", + () => resolve(), + { + once: true, + } + ); + }); + } + + requestFullscreen() { + let doc = this.contentWindow.document; + let button = doc.createElement("button"); + doc.body.appendChild(button); + + return new Promise(resolve => { + button.onclick = () => { + doc.body.requestFullscreen().then(resolve); + doc.body.removeChild(button); + }; + button.click(); + }); + } + + receiveMessage(msg) { + switch (msg.name) { + case "WaitForChange": + return this.changed(); + case "ExitFullscreen": + return this.contentWindow.document.exitFullscreen(); + case "RequestFullscreen": + this.browsingContext.isActive = true; + return Promise.all([this.changed(), this.requestFullscreen()]); + case "CreateChild": + let child = msg.data; + let iframe = this.contentWindow.document.createElement("iframe"); + iframe.allow = child.allow_fullscreen ? "fullscreen" : ""; + iframe.name = child.name; + + let loaded = new Promise(resolve => { + iframe.addEventListener( + "load", + () => resolve(iframe.browsingContext), + { once: true } + ); + }); + iframe.src = child.url; + this.contentWindow.document.body.appendChild(iframe); + return loaded; + case "GetEvents": + return Promise.resolve(this.fullscreen_events); + case "ClearEvents": + this.fullscreen_events = []; + return Promise.resolve(); + case "GetFullscreenElement": + let document = this.contentWindow.document; + let child_iframe = this.contentWindow.document.getElementsByTagName( + "iframe" + ) + ? this.contentWindow.document.getElementsByTagName("iframe")[0] + : null; + switch (document.fullscreenElement) { + case null: + return Promise.resolve("null"); + case document: + return Promise.resolve("document"); + case document.body: + return Promise.resolve("body"); + case child_iframe: + return Promise.resolve("child_iframe"); + default: + return Promise.resolve("other"); + } + } + + return Promise.reject("Unexpected Message"); + } + + async handleEvent(event) { + switch (event.type) { + case "fullscreenchange": + this.fullscreen_events.push(true); + break; + case "fullscreenerror": + this.fullscreen_events.push(false); + break; + } + } +} diff --git a/browser/base/content/test/fullscreen/browser.ini b/browser/base/content/test/fullscreen/browser.ini new file mode 100644 index 0000000000..8eecb3f99c --- /dev/null +++ b/browser/base/content/test/fullscreen/browser.ini @@ -0,0 +1,31 @@ +[DEFAULT] +support-files = + head.js + open_and_focus_helper.html + +[browser_bug1557041.js] +[browser_bug1620341.js] +support-files = fullscreen.html fullscreen_frame.html +[browser_fullscreen_api_fission.js] +https_first_disabled = true +support-files = fullscreen.html FullscreenFrame.sys.mjs +[browser_fullscreen_context_menu.js] +[browser_fullscreen_cross_origin.js] +support-files = fullscreen.html fullscreen_frame.html +[browser_fullscreen_enterInUrlbar.js] +skip-if = (os == 'mac') || (os == 'linux') # Bug 1648649 +[browser_fullscreen_from_minimize.js] +skip-if = (os == 'linux') || (os == 'win') # Bug 1818795 and Bug 1818796 +[browser_fullscreen_keydown_reservation.js] +[browser_fullscreen_menus.js] +[browser_fullscreen_newtab.js] +[browser_fullscreen_newwindow.js] +[browser_fullscreen_permissions_prompt.js] +[browser_fullscreen_warning.js] +support-files = fullscreen.html +skip-if = + (os == 'mac') # Bug 1848423 +[browser_fullscreen_window_focus.js] +skip-if = (os == 'mac') && debug # Bug 1568570 +[browser_fullscreen_window_open.js] +skip-if = (os == 'linux') && swgl # Bug 1795491 diff --git a/browser/base/content/test/fullscreen/browser_bug1557041.js b/browser/base/content/test/fullscreen/browser_bug1557041.js new file mode 100644 index 0000000000..3f00de86a0 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_bug1557041.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test tends to trigger a race in the fullscreen time telemetry, +// where the fullscreen enter and fullscreen exit events (which use the +// same histogram ID) overlap. That causes TelemetryStopwatch to log an +// error. +SimpleTest.ignoreAllUncaughtExceptions(true); + +add_task(async function test_identityPopupCausesFSExit() { + let url = "https://example.com/"; + + await BrowserTestUtils.withNewTab("about:blank", async browser => { + let loaded = BrowserTestUtils.browserLoaded(browser, false, url); + BrowserTestUtils.loadURIString(browser, url); + await loaded; + + let identityPermissionBox = document.getElementById( + "identity-permission-box" + ); + + info("Entering DOM fullscreen"); + await changeFullscreen(browser, true); + + let popupShown = BrowserTestUtils.waitForEvent( + window, + "popupshown", + true, + event => event.target == document.getElementById("permission-popup") + ); + let fsExit = waitForFullScreenState(browser, false); + + identityPermissionBox.click(); + + info("Waiting for fullscreen exit and permission popup to show"); + await Promise.all([fsExit, popupShown]); + + let identityPopup = document.getElementById("permission-popup"); + ok( + identityPopup.hasAttribute("panelopen"), + "Identity popup should be open" + ); + ok(!window.fullScreen, "Should not be in full-screen"); + }); +}); diff --git a/browser/base/content/test/fullscreen/browser_bug1620341.js b/browser/base/content/test/fullscreen/browser_bug1620341.js new file mode 100644 index 0000000000..bd836eb5a3 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_bug1620341.js @@ -0,0 +1,108 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const tab1URL = `data:text/html, + <html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta charset="utf-8"/> + <title>First tab to be loaded</title> + </head> + <body> + <button>JUST A BUTTON</button> + </body> + </html>`; + +const ORIGIN = + "https://example.com/browser/browser/base/content/test/fullscreen/fullscreen_frame.html"; + +add_task(async function test_fullscreen_cross_origin() { + async function requestFullscreenThenCloseTab() { + await BrowserTestUtils.withNewTab(ORIGIN, async function (browser) { + info("Start fullscreen on iframe frameAllowed"); + + // Make sure there is no attribute "inDOMFullscreen" before requesting fullscreen. + await TestUtils.waitForCondition( + () => !document.documentElement.hasAttribute("inDOMFullscreen") + ); + + let tabbrowser = browser.ownerDocument.querySelector("#tabbrowser-tabs"); + ok( + !tabbrowser.hasAttribute("closebuttons"), + "Close buttons should be visible on every tab" + ); + + // Request fullscreen from iframe + await SpecialPowers.spawn(browser, [], async function () { + let frame = content.document.getElementById("frameAllowed"); + frame.focus(); + await SpecialPowers.spawn(frame, [], async () => { + let frameDoc = content.document; + const waitForFullscreen = new Promise(resolve => { + const message = "fullscreenchange"; + function handler(evt) { + frameDoc.removeEventListener(message, handler); + Assert.equal(evt.type, message, `Request should be allowed`); + resolve(); + } + frameDoc.addEventListener(message, handler); + }); + + frameDoc.getElementById("request").click(); + await waitForFullscreen; + }); + }); + + // Make sure there is attribute "inDOMFullscreen" after requesting fullscreen. + await TestUtils.waitForCondition(() => + document.documentElement.hasAttribute("inDOMFullscreen") + ); + + await TestUtils.waitForCondition( + () => tabbrowser.hasAttribute("closebuttons"), + "Close buttons should be visible only on the active tab (tabs have width=0 so closebuttons gets set on them)" + ); + }); + } + + await SpecialPowers.pushPrefEnv({ + set: [ + ["full-screen-api.enabled", true], + ["full-screen-api.allow-trusted-requests-only", false], + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"], + ["dom.security.featurePolicy.header.enabled", true], + ["dom.security.featurePolicy.webidl.enabled", true], + ], + }); + + // Open a tab with tab1URL. + let tab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + tab1URL, + true + ); + + // 1. Open another tab and load a page with two iframes. + // 2. Request fullscreen from an iframe which is in a different origin. + // 3. Close the tab after receiving "fullscreenchange" message. + // Note that we don't do "doc.exitFullscreen()" before closing the tab + // on purpose. + await requestFullscreenThenCloseTab(); + + // Wait until attribute "inDOMFullscreen" is removed. + await TestUtils.waitForCondition( + () => !document.documentElement.hasAttribute("inDOMFullscreen") + ); + + await TestUtils.waitForCondition( + () => !gBrowser.tabContainer.hasAttribute("closebuttons"), + "Close buttons should come back to every tab" + ); + + // Remove the remaining tab and leave the test. + let tabClosed = BrowserTestUtils.waitForTabClosing(tab1); + BrowserTestUtils.removeTab(tab1); + await tabClosed; +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js b/browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js new file mode 100644 index 0000000000..03b65ddc0e --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_api_fission.js @@ -0,0 +1,252 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* This test checks that `document.fullscreenElement` is set correctly and + * proper fullscreenchange events fire when an element inside of a + * multi-origin tree of iframes calls `requestFullscreen()`. It is designed + * to make sure the fullscreen API is working properly in fission when the + * frame tree spans multiple processes. + * + * A similarly purposed Web Platform Test exists, but at the time of writing + * is manual, so it cannot be run in CI: + * `element-request-fullscreen-cross-origin-manual.sub.html` + */ + +"use strict"; + +const actorModuleURI = getRootDirectory(gTestPath) + "FullscreenFrame.sys.mjs"; +const actorName = "FullscreenFrame"; + +const fullscreenPath = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", "") + + "fullscreen.html"; + +const fullscreenTarget = "D"; +// TOP +// | \ +// A B +// | +// C +// | +// D +// | +// E +const frameTree = { + name: "TOP", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: `http://example.com${fullscreenPath}`, + allow_fullscreen: true, + children: [ + { + name: "A", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: `http://example.org${fullscreenPath}`, + allow_fullscreen: true, + children: [ + { + name: "C", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: `http://example.com${fullscreenPath}`, + allow_fullscreen: true, + children: [ + { + name: "D", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: `http://example.com${fullscreenPath}?different-uri=1`, + allow_fullscreen: true, + children: [ + { + name: "E", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: `http://example.org${fullscreenPath}`, + allow_fullscreen: true, + children: [], + }, + ], + }, + ], + }, + ], + }, + { + name: "B", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + url: `http://example.net${fullscreenPath}`, + allow_fullscreen: true, + children: [], + }, + ], +}; + +add_task(async function test_fullscreen_api_cross_origin_tree() { + await new Promise(r => { + SpecialPowers.pushPrefEnv( + { + set: [ + ["full-screen-api.enabled", true], + ["full-screen-api.allow-trusted-requests-only", false], + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"], + ["dom.security.featurePolicy.header.enabled", true], + ["dom.security.featurePolicy.webidl.enabled", true], + ], + }, + r + ); + }); + + // Register a custom window actor to handle tracking events + // and constructing subframes + ChromeUtils.registerWindowActor(actorName, { + child: { + esModuleURI: actorModuleURI, + events: { + fullscreenchange: { mozSystemGroup: true, capture: true }, + fullscreenerror: { mozSystemGroup: true, capture: true }, + }, + }, + allFrames: true, + }); + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: frameTree.url, + }); + + let frames = new Map(); + async function construct_frame_children(browsingContext, tree) { + let actor = browsingContext.currentWindowGlobal.getActor(actorName); + frames.set(tree.name, { + browsingContext, + actor, + }); + + for (let child of tree.children) { + // Create the child IFrame and wait for it to load. + let childBC = await actor.sendQuery("CreateChild", child); + await construct_frame_children(childBC, child); + } + } + + await construct_frame_children(tab.linkedBrowser.browsingContext, frameTree); + + async function check_events(expected_events) { + for (let [name, expected] of expected_events) { + let actor = frames.get(name).actor; + + // Each content process fires the fullscreenchange + // event independently and in parallel making it + // possible for the promises returned by + // `requestFullscreen` or `exitFullscreen` to + // resolve before all events have fired. We wait + // for the number of events to match before + // continuing to ensure we don't miss an expected + // event that hasn't fired yet. + let events; + await TestUtils.waitForCondition(async () => { + events = await actor.sendQuery("GetEvents"); + return events.length == expected.length; + }, `Waiting for number of events to match`); + + Assert.equal(events.length, expected.length, "Number of events equal"); + events.forEach((value, i) => { + Assert.equal(value, expected[i], "Event type matches"); + }); + } + } + + async function check_fullscreenElement(expected_elements) { + for (let [name, expected] of expected_elements) { + let element = await frames + .get(name) + .actor.sendQuery("GetFullscreenElement"); + Assert.equal(element, expected, "The fullScreenElement matches"); + } + } + + // Trigger fullscreen from the target frame. + let target = frames.get(fullscreenTarget); + await target.actor.sendQuery("RequestFullscreen"); + // true is fullscreenchange and false is fullscreenerror. + await check_events( + new Map([ + ["TOP", [true]], + ["A", [true]], + ["B", []], + ["C", [true]], + ["D", [true]], + ["E", []], + ]) + ); + await check_fullscreenElement( + new Map([ + ["TOP", "child_iframe"], + ["A", "child_iframe"], + ["B", "null"], + ["C", "child_iframe"], + ["D", "body"], + ["E", "null"], + ]) + ); + + await target.actor.sendQuery("ExitFullscreen"); + // fullscreenchange should have fired on exit as well. + // true is fullscreenchange and false is fullscreenerror. + await check_events( + new Map([ + ["TOP", [true, true]], + ["A", [true, true]], + ["B", []], + ["C", [true, true]], + ["D", [true, true]], + ["E", []], + ]) + ); + await check_fullscreenElement( + new Map([ + ["TOP", "null"], + ["A", "null"], + ["B", "null"], + ["C", "null"], + ["D", "null"], + ["E", "null"], + ]) + ); + + // Clear previous events before testing exiting fullscreen with ESC. + for (const frame of frames.values()) { + frame.actor.sendQuery("ClearEvents"); + } + await target.actor.sendQuery("RequestFullscreen"); + + // Escape should cause the proper events to fire and + // document.fullscreenElement should be cleared. + let finished_exiting = target.actor.sendQuery("WaitForChange"); + EventUtils.sendKey("ESCAPE"); + await finished_exiting; + // true is fullscreenchange and false is fullscreenerror. + await check_events( + new Map([ + ["TOP", [true, true]], + ["A", [true, true]], + ["B", []], + ["C", [true, true]], + ["D", [true, true]], + ["E", []], + ]) + ); + await check_fullscreenElement( + new Map([ + ["TOP", "null"], + ["A", "null"], + ["B", "null"], + ["C", "null"], + ["D", "null"], + ["E", "null"], + ]) + ); + + // Remove the tests custom window actor. + ChromeUtils.unregisterWindowActor("FullscreenFrame"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_context_menu.js b/browser/base/content/test/fullscreen/browser_fullscreen_context_menu.js new file mode 100644 index 0000000000..ec874f1a3f --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_context_menu.js @@ -0,0 +1,142 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function openContextMenu(itemElement, win = window) { + let popupShownPromise = BrowserTestUtils.waitForEvent( + itemElement.ownerDocument, + "popupshown" + ); + EventUtils.synthesizeMouseAtCenter( + itemElement, + { + type: "contextmenu", + button: 2, + }, + win + ); + let { target } = await popupShownPromise; + return target; +} + +async function testContextMenu() { + await BrowserTestUtils.withNewTab("about:blank", async () => { + let panelUIMenuButton = document.getElementById("PanelUI-menu-button"); + let contextMenu = await openContextMenu(panelUIMenuButton); + let array1 = AppConstants.MENUBAR_CAN_AUTOHIDE + ? [ + ".customize-context-moveToPanel", + ".customize-context-removeFromToolbar", + "#toolbarItemsMenuSeparator", + "#toggle_toolbar-menubar", + "#toggle_PersonalToolbar", + "#viewToolbarsMenuSeparator", + ".viewCustomizeToolbar", + ] + : [ + ".customize-context-moveToPanel", + ".customize-context-removeFromToolbar", + "#toolbarItemsMenuSeparator", + "#toggle_PersonalToolbar", + "#viewToolbarsMenuSeparator", + ".viewCustomizeToolbar", + ]; + let result1 = verifyContextMenu(contextMenu, array1); + ok(!result1, "Expected no errors verifying context menu items"); + contextMenu.hidePopup(); + let onFullscreen = Promise.all([ + BrowserTestUtils.waitForEvent(window, "fullscreen"), + BrowserTestUtils.waitForEvent( + window, + "sizemodechange", + false, + e => window.fullScreen + ), + BrowserTestUtils.waitForPopupEvent(contextMenu, "hidden"), + ]); + document.getElementById("View:FullScreen").doCommand(); + contextMenu.hidePopup(); + info("waiting for fullscreen"); + await onFullscreen; + // make sure the toolbox is visible if it's autohidden + document.getElementById("Browser:OpenLocation").doCommand(); + info("trigger the context menu"); + let contextMenu2 = await openContextMenu(panelUIMenuButton); + info("context menu should be open, verify its menu items"); + let array2 = AppConstants.MENUBAR_CAN_AUTOHIDE + ? [ + ".customize-context-moveToPanel", + ".customize-context-removeFromToolbar", + "#toolbarItemsMenuSeparator", + "#toggle_toolbar-menubar", + "#toggle_PersonalToolbar", + "#viewToolbarsMenuSeparator", + ".viewCustomizeToolbar", + `menuseparator[contexttype="fullscreen"]`, + `.fullscreen-context-autohide`, + `menuitem[contexttype="fullscreen"]`, + ] + : [ + ".customize-context-moveToPanel", + ".customize-context-removeFromToolbar", + "#toolbarItemsMenuSeparator", + "#toggle_PersonalToolbar", + "#viewToolbarsMenuSeparator", + ".viewCustomizeToolbar", + `menuseparator[contexttype="fullscreen"]`, + `.fullscreen-context-autohide`, + `menuitem[contexttype="fullscreen"]`, + ]; + let result2 = verifyContextMenu(contextMenu2, array2); + ok(!result2, "Expected no errors verifying context menu items"); + let onExitFullscreen = Promise.all([ + BrowserTestUtils.waitForEvent(window, "fullscreen"), + BrowserTestUtils.waitForEvent( + window, + "sizemodechange", + false, + e => !window.fullScreen + ), + BrowserTestUtils.waitForPopupEvent(contextMenu2, "hidden"), + ]); + document.getElementById("View:FullScreen").doCommand(); + contextMenu2.hidePopup(); + await onExitFullscreen; + }); +} + +function verifyContextMenu(contextMenu, itemSelectors) { + // Ignore hidden nodes + let items = Array.from(contextMenu.children).filter(n => + BrowserTestUtils.is_visible(n) + ); + let menuAsText = items + .map(n => { + return n.nodeName == "menuseparator" + ? "---" + : `${n.label} (${n.command})`; + }) + .join("\n"); + info("Got actual context menu items: \n" + menuAsText); + + try { + is( + items.length, + itemSelectors.length, + "Context menu has the expected number of items" + ); + for (let i = 0; i < items.length; i++) { + let selector = itemSelectors[i]; + ok( + items[i].matches(selector), + `Item at ${i} matches expected selector: ${selector}` + ); + } + } catch (ex) { + return ex; + } + return null; +} + +add_task(testContextMenu); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_cross_origin.js b/browser/base/content/test/fullscreen/browser_fullscreen_cross_origin.js new file mode 100644 index 0000000000..0babb8b35e --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_cross_origin.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const ORIGIN = + "https://example.com/browser/browser/base/content/test/fullscreen/fullscreen_frame.html"; + +add_task(async function test_fullscreen_cross_origin() { + async function requestFullscreen(aAllow, aExpect) { + await BrowserTestUtils.withNewTab(ORIGIN, async function (browser) { + const iframeId = aExpect == "allowed" ? "frameAllowed" : "frameDenied"; + + info("Start fullscreen on iframe " + iframeId); + await SpecialPowers.spawn( + browser, + [{ aExpect, iframeId }], + async function (args) { + let frame = content.document.getElementById(args.iframeId); + frame.focus(); + await SpecialPowers.spawn(frame, [args.aExpect], async expect => { + let frameDoc = content.document; + const waitForFullscreen = new Promise(resolve => { + const message = + expect == "allowed" ? "fullscreenchange" : "fullscreenerror"; + function handler(evt) { + frameDoc.removeEventListener(message, handler); + Assert.equal(evt.type, message, `Request should be ${expect}`); + frameDoc.exitFullscreen(); + resolve(); + } + frameDoc.addEventListener(message, handler); + }); + frameDoc.getElementById("request").click(); + await waitForFullscreen; + }); + } + ); + + if (aExpect == "allowed") { + waitForFullScreenState(browser, false); + } + }); + } + + await new Promise(r => { + SpecialPowers.pushPrefEnv( + { + set: [ + ["full-screen-api.enabled", true], + ["full-screen-api.allow-trusted-requests-only", false], + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"], + ["dom.security.featurePolicy.header.enabled", true], + ["dom.security.featurePolicy.webidl.enabled", true], + ], + }, + r + ); + }); + + await requestFullscreen(undefined, "denied"); + await requestFullscreen("fullscreen", "allowed"); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_enterInUrlbar.js b/browser/base/content/test/fullscreen/browser_fullscreen_enterInUrlbar.js new file mode 100644 index 0000000000..6ece64a6f3 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_enterInUrlbar.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test makes sure that when the user presses enter in the urlbar in full +// screen, the toolbars are hidden. This should not be run on macOS because we +// don't hide the toolbars there. + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs", +}); + +add_task(async function test() { + await BrowserTestUtils.withNewTab("about:blank", async () => { + // Do the View:FullScreen command and wait for the transition. + let onFullscreen = BrowserTestUtils.waitForEvent(window, "fullscreen"); + document.getElementById("View:FullScreen").doCommand(); + await onFullscreen; + + // Do the Browser:OpenLocation command to show the nav toolbox and focus + // the urlbar. + let onToolboxShown = TestUtils.topicObserved( + "fullscreen-nav-toolbox", + (subject, data) => data == "shown" + ); + document.getElementById("Browser:OpenLocation").doCommand(); + info("Waiting for the nav toolbox to be shown"); + await onToolboxShown; + + // Enter a URL. + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + value: "http://example.com/", + waitForFocus: SimpleTest.waitForFocus, + fireInputEvent: true, + }); + + // Press enter and wait for the nav toolbox to be hidden. + let onToolboxHidden = TestUtils.topicObserved( + "fullscreen-nav-toolbox", + (subject, data) => data == "hidden" + ); + EventUtils.synthesizeKey("KEY_Enter"); + info("Waiting for the nav toolbox to be hidden"); + await onToolboxHidden; + + Assert.ok(true, "Nav toolbox hidden"); + + info("Waiting for exiting from the fullscreen mode..."); + onFullscreen = BrowserTestUtils.waitForEvent(window, "fullscreen"); + document.getElementById("View:FullScreen").doCommand(); + await onFullscreen; + }); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_from_minimize.js b/browser/base/content/test/fullscreen/browser_fullscreen_from_minimize.js new file mode 100644 index 0000000000..c4ef8fe642 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_from_minimize.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test checks whether fullscreen windows can transition to minimized windows, +// and back again. This is sometimes not directly supported by the OS widgets. For +// example, in macOS, the minimize button is greyed-out in the title bar of +// fullscreen windows, making this transition impossible for users to initiate. +// Still, web APIs do allow arbitrary combinations of window calls, and this test +// exercises some of those combinations. + +const restoreWindowToNormal = async () => { + // Get the window to normal state by calling window.restore(). This may take + // multiple attempts since a call to restore could bring the window to either + // NORMAL or MAXIMIZED state. + while (window.windowState != window.STATE_NORMAL) { + info( + `Calling window.restore(), to try to reach "normal" state ${window.STATE_NORMAL}.` + ); + let promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.restore(); + await promiseSizeModeChange; + info(`Window reached state ${window.windowState}.`); + } +}; + +add_task(async function () { + registerCleanupFunction(function () { + window.restore(); + }); + + // We reuse these variables to create new promises for each transition. + let promiseSizeModeChange; + let promiseFullscreen; + + await restoreWindowToNormal(); + ok(!window.fullScreen, "Window should not be fullscreen at start of test."); + + // Get to fullscreen. + info("Requesting fullscreen."); + promiseFullscreen = document.documentElement.requestFullscreen(); + await promiseFullscreen; + ok(window.fullScreen, "Window should be fullscreen before being minimized."); + + // Transition between fullscreen and minimize states. + info("Requesting minimize on a fullscreen window."); + promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.minimize(); + await promiseSizeModeChange; + is( + window.windowState, + window.STATE_MINIMIZED, + "Window should be minimized after fullscreen." + ); + + // Whether or not the previous transition worked, restore the window + // and then minimize it. + await restoreWindowToNormal(); + + info("Requesting minimize on a normal window."); + promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.minimize(); + await promiseSizeModeChange; + is( + window.windowState, + window.STATE_MINIMIZED, + "Window should be minimized before fullscreen." + ); + + info("Requesting fullscreen on a minimized window."); + promiseFullscreen = document.documentElement.requestFullscreen(); + await promiseFullscreen; + ok(window.fullScreen, "Window should be fullscreen after being minimized."); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_keydown_reservation.js b/browser/base/content/test/fullscreen/browser_fullscreen_keydown_reservation.js new file mode 100644 index 0000000000..2d34ac6c7b --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_keydown_reservation.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test verifies that whether shortcut keys of toggling fullscreen modes +// are reserved. +add_task(async function test_keydown_event_reservation_toggling_fullscreen() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"], + ], + }); + + let shortcutKeys = [{ key: "KEY_F11", modifiers: {} }]; + if (navigator.platform.startsWith("Mac")) { + shortcutKeys.push({ + key: "f", + modifiers: { metaKey: true, ctrlKey: true }, + }); + shortcutKeys.push({ + key: "F", + modifiers: { metaKey: true, shiftKey: true }, + }); + } + function shortcutDescription(aShortcutKey) { + return `${ + aShortcutKey.metaKey ? "Meta + " : "" + }${aShortcutKey.shiftKey ? "Shift + " : ""}${aShortcutKey.ctrlKey ? "Ctrl + " : ""}${aShortcutKey.key.replace("KEY_", "")}`; + } + for (const shortcutKey of shortcutKeys) { + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html" + ); + + await SimpleTest.promiseFocus(tab.linkedBrowser); + + const fullScreenEntered = BrowserTestUtils.waitForEvent( + window, + "fullscreen" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + content.wrappedJSObject.keydown = null; + content.window.addEventListener("keydown", event => { + switch (event.key) { + case "Shift": + case "Meta": + case "Control": + break; + default: + content.wrappedJSObject.keydown = event; + } + }); + }); + + EventUtils.synthesizeKey(shortcutKey.key, shortcutKey.modifiers); + + info( + `Waiting for entering the fullscreen mode with synthesizing ${shortcutDescription( + shortcutKey + )}...` + ); + await fullScreenEntered; + + info("Retrieving the result..."); + Assert.ok( + await SpecialPowers.spawn( + tab.linkedBrowser, + [], + async () => !!content.wrappedJSObject.keydown + ), + `Entering the fullscreen mode with ${shortcutDescription( + shortcutKey + )} should cause "keydown" event` + ); + + const fullScreenExited = BrowserTestUtils.waitForEvent( + window, + "fullscreen" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + content.wrappedJSObject.keydown = null; + }); + + EventUtils.synthesizeKey(shortcutKey.key, shortcutKey.modifiers); + + info( + `Waiting for exiting from the fullscreen mode with synthesizing ${shortcutDescription( + shortcutKey + )}...` + ); + await fullScreenExited; + + info("Retrieving the result..."); + Assert.ok( + await SpecialPowers.spawn( + tab.linkedBrowser, + [], + async () => !content.wrappedJSObject.keydown + ), + `Exiting from the fullscreen mode with ${shortcutDescription( + shortcutKey + )} should not cause "keydown" event` + ); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_menus.js b/browser/base/content/test/fullscreen/browser_fullscreen_menus.js new file mode 100644 index 0000000000..90dd06192d --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_menus.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_shortcut_key_label_in_fullscreen_menu_item() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["full-screen-api.transition-duration.enter", "0 0"], + ["full-screen-api.transition-duration.leave", "0 0"], + ], + }); + + const isMac = AppConstants.platform == "macosx"; + const shortCutKeyLabel = isMac ? "\u2303\u2318F" : "F11"; + const enterMenuItemId = isMac ? "enterFullScreenItem" : "fullScreenItem"; + const exitMenuItemId = isMac ? "exitFullScreenItem" : "fullScreenItem"; + const accelKeyLabelSelector = ".menu-accel-container > label"; + + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html" + ); + + await SimpleTest.promiseFocus(tab.linkedBrowser); + + document.getElementById(enterMenuItemId).render(); + Assert.equal( + document + .getElementById(enterMenuItemId) + .querySelector(accelKeyLabelSelector) + ?.getAttribute("value"), + shortCutKeyLabel, + `The menu item to enter into the fullscreen mode should show a shortcut key` + ); + + const fullScreenEntered = BrowserTestUtils.waitForEvent(window, "fullscreen"); + + EventUtils.synthesizeKey("KEY_F11", {}); + + info(`Waiting for entering the fullscreen mode...`); + await fullScreenEntered; + + document.getElementById(exitMenuItemId).render(); + Assert.equal( + document + .getElementById(exitMenuItemId) + .querySelector(accelKeyLabelSelector) + ?.getAttribute("value"), + shortCutKeyLabel, + `The menu item to exiting from the fullscreen mode should show a shortcut key` + ); + + const fullScreenExited = BrowserTestUtils.waitForEvent(window, "fullscreen"); + + EventUtils.synthesizeKey("KEY_F11", {}); + + info(`Waiting for exiting from the fullscreen mode...`); + await fullScreenExited; + + document.getElementById(enterMenuItemId).render(); + Assert.equal( + document + .getElementById(enterMenuItemId) + .querySelector(accelKeyLabelSelector) + ?.getAttribute("value"), + shortCutKeyLabel, + `After exiting from the fullscreen mode, the menu item to enter the fullscreen mode should show a shortcut key` + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_newtab.js b/browser/base/content/test/fullscreen/browser_fullscreen_newtab.js new file mode 100644 index 0000000000..d5a74a0aa3 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_newtab.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test verifies that when in fullscreen mode, and a new tab is opened, +// fullscreen mode is exited and the url bar is focused. +add_task(async function test_fullscreen_display_none() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["full-screen-api.enabled", true], + ["full-screen-api.allow-trusted-requests-only", false], + ], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html" + ); + + let fullScreenEntered = BrowserTestUtils.waitForEvent( + document, + "fullscreenchange", + false, + () => document.fullscreenElement + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + content.document.getElementById("request").click(); + }); + + await fullScreenEntered; + + let fullScreenExited = BrowserTestUtils.waitForEvent( + document, + "fullscreenchange", + false, + () => !document.fullscreenElement + ); + + let focusPromise = BrowserTestUtils.waitForEvent(window, "focus"); + EventUtils.synthesizeKey("T", { accelKey: true }); + await focusPromise; + + is( + document.activeElement, + gURLBar.inputField, + "url bar is focused after new tab opened" + ); + + await fullScreenExited; + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_newwindow.js b/browser/base/content/test/fullscreen/browser_fullscreen_newwindow.js new file mode 100644 index 0000000000..dee02e2db0 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_newwindow.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test verifies that when in fullscreen mode, and a new window is opened, +// fullscreen mode should not exit and the url bar is focused. +add_task(async function test_fullscreen_new_window() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["full-screen-api.enabled", true], + ["full-screen-api.allow-trusted-requests-only", false], + ], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html" + ); + + let fullScreenEntered = BrowserTestUtils.waitForEvent( + document, + "fullscreenchange", + false, + () => document.fullscreenElement + ); + + // Enter fullscreen. + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + content.document.getElementById("request").click(); + }); + + await fullScreenEntered; + + // Open a new window via ctrl+n. + let newWindowPromise = BrowserTestUtils.waitForNewWindow({ + url: "about:blank", + }); + EventUtils.synthesizeKey("N", { accelKey: true }); + let newWindow = await newWindowPromise; + + // Check new window state. + is( + newWindow.document.activeElement, + newWindow.gURLBar.inputField, + "url bar is focused after new window opened" + ); + ok( + !newWindow.fullScreen, + "The new chrome window should not be in fullscreen" + ); + ok( + !newWindow.document.documentElement.hasAttribute("inDOMFullscreen"), + "The new chrome document should not be in fullscreen" + ); + + // Wait a bit then check the original window state. + await new Promise(resolve => TestUtils.executeSoon(resolve)); + ok( + window.fullScreen, + "The original chrome window should be still in fullscreen" + ); + ok( + document.documentElement.hasAttribute("inDOMFullscreen"), + "The original chrome document should be still in fullscreen" + ); + + // Close new window and move focus back to original window. + await BrowserTestUtils.closeWindow(newWindow); + await SimpleTest.promiseFocus(window); + + // Exit fullscreen on original window. + let fullScreenExited = BrowserTestUtils.waitForEvent( + document, + "fullscreenchange", + false, + () => !document.fullscreenElement + ); + EventUtils.synthesizeKey("KEY_Escape"); + await fullScreenExited; + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js b/browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js new file mode 100644 index 0000000000..82f0c97631 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_permissions_prompt.js @@ -0,0 +1,160 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test tends to trigger a race in the fullscreen time telemetry, +// where the fullscreen enter and fullscreen exit events (which use the +// same histogram ID) overlap. That causes TelemetryStopwatch to log an +// error. +SimpleTest.ignoreAllUncaughtExceptions(true); +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally(/Not in fullscreen mode/); + +SimpleTest.requestCompleteLog(); + +async function requestNotificationPermission(browser) { + return SpecialPowers.spawn(browser, [], () => { + return content.Notification.requestPermission(); + }); +} + +async function requestCameraPermission(browser) { + return SpecialPowers.spawn(browser, [], () => + content.navigator.mediaDevices + .getUserMedia({ video: true, fake: true }) + .then( + () => true, + () => false + ) + ); +} + +add_task(async function test_fullscreen_closes_permissionui_prompt() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.webnotifications.requireuserinteraction", false], + ["permissions.fullscreen.allowed", false], + ], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + let browser = tab.linkedBrowser; + + let popupShown, requestResult, popupHidden; + + popupShown = BrowserTestUtils.waitForEvent( + window.PopupNotifications.panel, + "popupshown" + ); + + info("Requesting notification permission"); + requestResult = requestNotificationPermission(browser); + await popupShown; + + info("Entering DOM full-screen"); + popupHidden = BrowserTestUtils.waitForEvent( + window.PopupNotifications.panel, + "popuphidden" + ); + + await changeFullscreen(browser, true); + + await popupHidden; + + is( + await requestResult, + "default", + "Expect permission request to be cancelled" + ); + + await changeFullscreen(browser, false); + + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_fullscreen_closes_webrtc_permission_prompt() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.navigator.permission.fake", true], + ["media.navigator.permission.force", true], + ["permissions.fullscreen.allowed", false], + ], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + let browser = tab.linkedBrowser; + let popupShown, requestResult, popupHidden; + + popupShown = BrowserTestUtils.waitForEvent( + window.PopupNotifications.panel, + "popupshown" + ); + + info("Requesting camera permission"); + requestResult = requestCameraPermission(browser); + + await popupShown; + + info("Entering DOM full-screen"); + popupHidden = BrowserTestUtils.waitForEvent( + window.PopupNotifications.panel, + "popuphidden" + ); + await changeFullscreen(browser, true); + + await popupHidden; + + is( + await requestResult, + false, + "Expect webrtc permission request to be cancelled" + ); + + await changeFullscreen(browser, false); + + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_permission_prompt_closes_fullscreen() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.webnotifications.requireuserinteraction", false], + ["permissions.fullscreen.allowed", false], + ], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + let browser = tab.linkedBrowser; + info("Entering DOM full-screen"); + await changeFullscreen(browser, true); + + let popupShown = BrowserTestUtils.waitForEvent( + window.PopupNotifications.panel, + "popupshown" + ); + let fullScreenExit = waitForFullScreenState(browser, false); + + info("Requesting notification permission"); + requestNotificationPermission(browser).catch(() => {}); + await popupShown; + + info("Waiting for full-screen exit"); + await fullScreenExit; + + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_warning.js b/browser/base/content/test/fullscreen/browser_fullscreen_warning.js new file mode 100644 index 0000000000..b8bab5f90c --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_warning.js @@ -0,0 +1,280 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function checkWarningState(aWarningElement, aExpectedState, aMsg) { + ["hidden", "ontop", "onscreen"].forEach(state => { + is( + aWarningElement.hasAttribute(state), + state == aExpectedState, + `${aMsg} - check ${state} attribute.` + ); + }); +} + +async function waitForWarningState(aWarningElement, aExpectedState) { + await BrowserTestUtils.waitForAttribute(aExpectedState, aWarningElement, ""); + checkWarningState( + aWarningElement, + aExpectedState, + `Wait for ${aExpectedState} state` + ); +} + +add_setup(async function init() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["full-screen-api.enabled", true], + ["full-screen-api.allow-trusted-requests-only", false], + ], + }); +}); + +add_task(async function test_fullscreen_display_none() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Fullscreen Test</title> + </head> + <body id="body"> + <iframe + src="https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html" + hidden + allowfullscreen></iframe> + </body> + </html>`, + }, + async function (browser) { + let warning = document.getElementById("fullscreen-warning"); + checkWarningState( + warning, + "hidden", + "Should not show full screen warning initially" + ); + + let warningShownPromise = waitForWarningState(warning, "onscreen"); + // Enter fullscreen + await SpecialPowers.spawn(browser, [], async () => { + let frame = content.document.querySelector("iframe"); + frame.focus(); + await SpecialPowers.spawn(frame, [], () => { + content.document.getElementById("request").click(); + }); + }); + await warningShownPromise; + ok(true, "Fullscreen warning shown"); + // Exit fullscreen + let exitFullscreenPromise = BrowserTestUtils.waitForEvent( + document, + "fullscreenchange", + false, + () => !document.fullscreenElement + ); + document.getElementById("fullscreen-exit-button").click(); + await exitFullscreenPromise; + + checkWarningState( + warning, + "hidden", + "Should hide fullscreen warning after exiting fullscreen" + ); + } + ); +}); + +add_task(async function test_fullscreen_pointerlock_conflict() { + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + let fsWarning = document.getElementById("fullscreen-warning"); + let plWarning = document.getElementById("pointerlock-warning"); + + checkWarningState( + fsWarning, + "hidden", + "Should not show full screen warning initially" + ); + checkWarningState( + plWarning, + "hidden", + "Should not show pointer lock warning initially" + ); + + let fsWarningShownPromise = waitForWarningState(fsWarning, "onscreen"); + info("Entering full screen and pointer lock."); + await SpecialPowers.spawn(browser, [], async () => { + await content.document.body.requestFullscreen(); + await content.document.body.requestPointerLock(); + }); + + await fsWarningShownPromise; + checkWarningState( + plWarning, + "hidden", + "Should not show pointer lock warning" + ); + + info("Exiting pointerlock"); + await SpecialPowers.spawn(browser, [], async () => { + await content.document.exitPointerLock(); + }); + + checkWarningState( + fsWarning, + "onscreen", + "Should still show full screen warning" + ); + checkWarningState( + plWarning, + "hidden", + "Should not show pointer lock warning" + ); + + // Cleanup + info("Exiting fullscreen"); + await document.exitFullscreen(); + }); +}); + +// https://bugzilla.mozilla.org/show_bug.cgi?id=1821884 +add_task(async function test_reshow_fullscreen_notification() { + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let fsWarning = document.getElementById("fullscreen-warning"); + + info("Entering full screen and wait for the fullscreen warning to appear."); + await SimpleTest.promiseFocus(window); + await Promise.all([ + waitForWarningState(fsWarning, "onscreen"), + BrowserTestUtils.waitForEvent(fsWarning, "transitionend"), + SpecialPowers.spawn(browser, [], async () => { + content.document.body.requestFullscreen(); + }), + ]); + + info( + "Switch focus away from the fullscreen window, the fullscreen warning should still hide automatically." + ); + await Promise.all([ + waitForWarningState(fsWarning, "hidden"), + SimpleTest.promiseFocus(newWin), + ]); + + info( + "Switch focus back to the fullscreen window, the fullscreen warning should show again." + ); + await Promise.all([ + waitForWarningState(fsWarning, "onscreen"), + SimpleTest.promiseFocus(window), + ]); + + info("Wait for fullscreen warning timed out."); + await waitForWarningState(fsWarning, "hidden"); + + info("The fullscreen warning should not show again."); + await SimpleTest.promiseFocus(newWin); + await SimpleTest.promiseFocus(window); + await new Promise(resolve => { + requestAnimationFrame(() => { + requestAnimationFrame(resolve); + }); + }); + checkWarningState( + fsWarning, + "hidden", + "The fullscreen warning should not show." + ); + + info("Close new browser window."); + await BrowserTestUtils.closeWindow(newWin); + + info("Exit fullscreen."); + await document.exitFullscreen(); + }); +}); + +add_task(async function test_fullscreen_reappear() { + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + let fsWarning = document.getElementById("fullscreen-warning"); + + info("Entering full screen and wait for the fullscreen warning to appear."); + await Promise.all([ + waitForWarningState(fsWarning, "onscreen"), + SpecialPowers.spawn(browser, [], async () => { + content.document.body.requestFullscreen(); + }), + ]); + + info("Wait for fullscreen warning timed out."); + await waitForWarningState(fsWarning, "hidden"); + + info("Move mouse to the top of screen."); + await Promise.all([ + waitForWarningState(fsWarning, "ontop"), + EventUtils.synthesizeMouse(document.documentElement, 100, 0, { + type: "mousemove", + }), + ]); + + info("Wait for fullscreen warning timed out again."); + await waitForWarningState(fsWarning, "hidden"); + + info("Exit fullscreen."); + await document.exitFullscreen(); + }); +}); + +// https://bugzilla.mozilla.org/show_bug.cgi?id=1847901 +add_task(async function test_fullscreen_warning_disabled() { + // Disable fullscreen warning + await SpecialPowers.pushPrefEnv({ + set: [["full-screen-api.warning.timeout", 0]], + }); + + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let fsWarning = document.getElementById("fullscreen-warning"); + let mut = new MutationObserver(mutations => { + ok(false, `${mutations[0].attributeName} attribute should not change`); + }); + mut.observe(fsWarning, { + attributeFilter: ["hidden", "onscreen", "ontop"], + }); + + info("Entering full screen."); + await SimpleTest.promiseFocus(window); + await SpecialPowers.spawn(browser, [], async () => { + return content.document.body.requestFullscreen(); + }); + // Wait a bit to ensure no state change. + await new Promise(resolve => { + requestAnimationFrame(() => { + requestAnimationFrame(resolve); + }); + }); + + info("The fullscreen warning should still not show after switching focus."); + await SimpleTest.promiseFocus(newWin); + await SimpleTest.promiseFocus(window); + // Wait a bit to ensure no state change. + await new Promise(resolve => { + requestAnimationFrame(() => { + requestAnimationFrame(resolve); + }); + }); + + mut.disconnect(); + + info("Close new browser window."); + await BrowserTestUtils.closeWindow(newWin); + + info("Exit fullscreen."); + await document.exitFullscreen(); + }); + + // Revert the setting to avoid affecting subsequent tests. + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js b/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js new file mode 100644 index 0000000000..7c935f64d3 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_window_focus.js @@ -0,0 +1,110 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function pause() { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + return new Promise(resolve => setTimeout(resolve, 500)); +} + +// This test tends to trigger a race in the fullscreen time telemetry, +// where the fullscreen enter and fullscreen exit events (which use the +// same histogram ID) overlap. That causes TelemetryStopwatch to log an +// error. +SimpleTest.ignoreAllUncaughtExceptions(true); + +const IFRAME_ID = "testIframe"; + +async function testWindowFocus(isPopup, iframeID) { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + info("Calling window.open()"); + let openedWindow = await jsWindowOpen(tab.linkedBrowser, isPopup, iframeID); + info("Letting OOP focus to stabilize"); + await pause(); // Bug 1719659 for proper fix + info("re-focusing main window"); + await waitForFocus(tab.linkedBrowser); + + info("Entering full-screen"); + await changeFullscreen(tab.linkedBrowser, true); + + await testExpectFullScreenExit(tab.linkedBrowser, true, async () => { + info("Calling window.focus()"); + await jsWindowFocus(tab.linkedBrowser, iframeID); + }); + + // Cleanup + if (isPopup) { + openedWindow.close(); + } else { + BrowserTestUtils.removeTab(openedWindow); + } + BrowserTestUtils.removeTab(tab); +} + +async function testWindowElementFocus(isPopup) { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + + info("Calling window.open()"); + let openedWindow = await jsWindowOpen(tab.linkedBrowser, isPopup); + info("Letting OOP focus to stabilize"); + await pause(); // Bug 1719659 for proper fix + info("re-focusing main window"); + await waitForFocus(tab.linkedBrowser); + + info("Entering full-screen"); + await changeFullscreen(tab.linkedBrowser, true); + + await testExpectFullScreenExit(tab.linkedBrowser, false, async () => { + info("Calling element.focus() on popup"); + await ContentTask.spawn(tab.linkedBrowser, {}, async args => { + await content.wrappedJSObject.sendMessage( + content.wrappedJSObject.openedWindow, + "elementfocus" + ); + }); + }); + + // Cleanup + await changeFullscreen(tab.linkedBrowser, false); + if (isPopup) { + openedWindow.close(); + } else { + BrowserTestUtils.removeTab(openedWindow); + } + BrowserTestUtils.removeTab(tab); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.disable_open_during_load", false], // Allow window.focus calls without user interaction + ["browser.link.open_newwindow.disabled_in_fullscreen", false], + ], + }); +}); + +add_task(function test_popupWindowFocus() { + return testWindowFocus(true); +}); + +add_task(function test_iframePopupWindowFocus() { + return testWindowFocus(true, IFRAME_ID); +}); + +add_task(function test_popupWindowElementFocus() { + return testWindowElementFocus(true); +}); + +add_task(function test_backgroundTabFocus() { + return testWindowFocus(false); +}); + +add_task(function test_iframebackgroundTabFocus() { + return testWindowFocus(false, IFRAME_ID); +}); + +add_task(function test_backgroundTabElementFocus() { + return testWindowElementFocus(false); +}); diff --git a/browser/base/content/test/fullscreen/browser_fullscreen_window_open.js b/browser/base/content/test/fullscreen/browser_fullscreen_window_open.js new file mode 100644 index 0000000000..aafed57c75 --- /dev/null +++ b/browser/base/content/test/fullscreen/browser_fullscreen_window_open.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test tends to trigger a race in the fullscreen time telemetry, +// where the fullscreen enter and fullscreen exit events (which use the +// same histogram ID) overlap. That causes TelemetryStopwatch to log an +// error. +SimpleTest.ignoreAllUncaughtExceptions(true); +SimpleTest.requestLongerTimeout(2); + +const IFRAME_ID = "testIframe"; + +async function testWindowOpen(iframeID) { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + info("Entering full-screen"); + await changeFullscreen(tab.linkedBrowser, true); + + let popup; + await testExpectFullScreenExit(tab.linkedBrowser, true, async () => { + info("Calling window.open()"); + popup = await jsWindowOpen(tab.linkedBrowser, true, iframeID); + }); + + // Cleanup + await BrowserTestUtils.closeWindow(popup); + BrowserTestUtils.removeTab(tab); +} + +async function testWindowOpenExistingWindow(funToOpenExitingWindow, iframeID) { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + let popup = await jsWindowOpen(tab.linkedBrowser, true); + + info("re-focusing main window"); + await waitForFocus(tab.linkedBrowser); + + info("Entering full-screen"); + await changeFullscreen(tab.linkedBrowser, true); + + info("open existing popup window"); + await testExpectFullScreenExit(tab.linkedBrowser, true, async () => { + await funToOpenExitingWindow(tab.linkedBrowser, iframeID); + }); + + // Cleanup + await BrowserTestUtils.closeWindow(popup); + BrowserTestUtils.removeTab(tab); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.disable_open_during_load", false], // Allow window.open calls without user interaction + ["browser.link.open_newwindow.disabled_in_fullscreen", false], + ], + }); +}); + +add_task(function test_parentWindowOpen() { + return testWindowOpen(); +}); + +add_task(function test_iframeWindowOpen() { + return testWindowOpen(IFRAME_ID); +}); + +add_task(async function test_parentWindowOpenExistWindow() { + await testWindowOpenExistingWindow(browser => { + info( + "Calling window.open() with same name again should reuse the existing window" + ); + jsWindowOpen(browser, true); + }); +}); + +add_task(async function test_iframeWindowOpenExistWindow() { + await testWindowOpenExistingWindow((browser, iframeID) => { + info( + "Calling window.open() with same name again should reuse the existing window" + ); + jsWindowOpen(browser, true, iframeID); + }, IFRAME_ID); +}); + +add_task(async function test_parentWindowClickLinkOpenExistWindow() { + await testWindowOpenExistingWindow(browser => { + info( + "Clicking link with same target name should reuse the existing window" + ); + jsClickLink(browser, true); + }); +}); + +add_task(async function test_iframeWindowClickLinkOpenExistWindow() { + await testWindowOpenExistingWindow((browser, iframeID) => { + info( + "Clicking link with same target name should reuse the existing window" + ); + jsClickLink(browser, true, iframeID); + }, IFRAME_ID); +}); diff --git a/browser/base/content/test/fullscreen/fullscreen.html b/browser/base/content/test/fullscreen/fullscreen.html new file mode 100644 index 0000000000..8b4289bb36 --- /dev/null +++ b/browser/base/content/test/fullscreen/fullscreen.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<script> +function requestFScreen() { + document.body.requestFullscreen(); +} +</script> +<body> +<button id="request" onclick="requestFScreen()"> Fullscreen </button> +<button id="focus"> Fullscreen </button> +</body> +</html> diff --git a/browser/base/content/test/fullscreen/fullscreen_frame.html b/browser/base/content/test/fullscreen/fullscreen_frame.html new file mode 100644 index 0000000000..ca1b1a4dd8 --- /dev/null +++ b/browser/base/content/test/fullscreen/fullscreen_frame.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html> +<body> + <iframe id="frameAllowed" + src="https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html" + allowfullscreen></iframe> + <iframe id="frameDenied" src="https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html"></iframe> +</body> +</html> diff --git a/browser/base/content/test/fullscreen/head.js b/browser/base/content/test/fullscreen/head.js new file mode 100644 index 0000000000..ce7a4b23b2 --- /dev/null +++ b/browser/base/content/test/fullscreen/head.js @@ -0,0 +1,164 @@ +const TEST_URL = + "https://example.com/browser/browser/base/content/test/fullscreen/open_and_focus_helper.html"; + +function waitForFullScreenState(browser, state) { + return new Promise(resolve => { + let eventReceived = false; + + let observe = (subject, topic, data) => { + if (!eventReceived) { + return; + } + Services.obs.removeObserver(observe, "fullscreen-painted"); + resolve(); + }; + Services.obs.addObserver(observe, "fullscreen-painted"); + + browser.ownerGlobal.addEventListener( + `MozDOMFullscreen:${state ? "Entered" : "Exited"}`, + () => { + eventReceived = true; + }, + { once: true } + ); + }); +} + +/** + * Spawns content task in browser to enter / leave fullscreen + * @param browser - Browser to use for JS fullscreen requests + * @param {Boolean} fullscreenState - true to enter fullscreen, false to leave + * @returns {Promise} - Resolves once fullscreen change is applied + */ +async function changeFullscreen(browser, fullScreenState) { + await new Promise(resolve => + SimpleTest.waitForFocus(resolve, browser.ownerGlobal) + ); + let fullScreenChange = waitForFullScreenState(browser, fullScreenState); + SpecialPowers.spawn(browser, [fullScreenState], async state => { + // Wait for document focus before requesting full-screen + await ContentTaskUtils.waitForCondition( + () => content.browsingContext.isActive && content.document.hasFocus(), + "Waiting for document focus" + ); + if (state) { + content.document.body.requestFullscreen(); + } else { + content.document.exitFullscreen(); + } + }); + return fullScreenChange; +} + +async function testExpectFullScreenExit(browser, leaveFS, action) { + let fsPromise = waitForFullScreenState(browser, false); + if (leaveFS) { + if (action) { + await action(); + } + await fsPromise; + ok(true, "Should leave full-screen"); + } else { + if (action) { + await action(); + } + let result = await Promise.race([ + fsPromise, + new Promise(resolve => { + SimpleTest.requestFlakyTimeout("Wait for failure condition"); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => resolve(true), 2500); + }), + ]); + ok(result, "Should not leave full-screen"); + } +} + +function jsWindowFocus(browser, iframeId) { + return ContentTask.spawn(browser, { iframeId }, async args => { + let destWin = content; + if (args.iframeId) { + let iframe = content.document.getElementById(args.iframeId); + if (!iframe) { + throw new Error("iframe not set"); + } + destWin = iframe.contentWindow; + } + await content.wrappedJSObject.sendMessage(destWin, "focus"); + }); +} + +function jsElementFocus(browser, iframeId) { + return ContentTask.spawn(browser, { iframeId }, async args => { + let destWin = content; + if (args.iframeId) { + let iframe = content.document.getElementById(args.iframeId); + if (!iframe) { + throw new Error("iframe not set"); + } + destWin = iframe.contentWindow; + } + await content.wrappedJSObject.sendMessage(destWin, "elementfocus"); + }); +} + +async function jsWindowOpen(browser, isPopup, iframeId) { + //let windowOpened = BrowserTestUtils.waitForNewWindow(); + let windowOpened = isPopup + ? BrowserTestUtils.waitForNewWindow({ url: TEST_URL }) + : BrowserTestUtils.waitForNewTab(gBrowser, TEST_URL, true); + ContentTask.spawn(browser, { isPopup, iframeId }, async args => { + let destWin = content; + if (args.iframeId) { + // Create a cross origin iframe + destWin = ( + await content.wrappedJSObject.createIframe(args.iframeId, true) + ).contentWindow; + } + // Send message to either the iframe or the current page to open a popup + await content.wrappedJSObject.sendMessage( + destWin, + args.isPopup ? "openpopup" : "open" + ); + }); + return windowOpened; +} + +async function jsClickLink(browser, isPopup, iframeId) { + //let windowOpened = BrowserTestUtils.waitForNewWindow(); + let windowOpened = isPopup + ? BrowserTestUtils.waitForNewWindow({ url: TEST_URL }) + : BrowserTestUtils.waitForNewTab(gBrowser, TEST_URL, true); + ContentTask.spawn(browser, { isPopup, iframeId }, async args => { + let destWin = content; + if (args.iframeId) { + // Create a cross origin iframe + destWin = ( + await content.wrappedJSObject.createIframe(args.iframeId, true) + ).contentWindow; + } + // Send message to either the iframe or the current page to click a link + await content.wrappedJSObject.sendMessage(destWin, "clicklink"); + }); + return windowOpened; +} + +function waitForFocus(...args) { + return new Promise(resolve => SimpleTest.waitForFocus(resolve, ...args)); +} + +function waitForBrowserWindowActive(win) { + return new Promise(resolve => { + if (Services.focus.activeWindow == win) { + resolve(); + } else { + win.addEventListener( + "activate", + () => { + resolve(); + }, + { once: true } + ); + } + }); +} diff --git a/browser/base/content/test/fullscreen/open_and_focus_helper.html b/browser/base/content/test/fullscreen/open_and_focus_helper.html new file mode 100644 index 0000000000..06d1800714 --- /dev/null +++ b/browser/base/content/test/fullscreen/open_and_focus_helper.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset='utf-8'> + <script src="/tests/SimpleTest/EventUtils.js"></script> +</head> +<body> + <input></input><br> + <a href="https://example.com" target="test">link</a> + <script> + const MY_ORIGIN = window.location.origin; + const CROSS_ORIGIN = "https://example.org"; + + // Creates an iframe with message channel to trigger window open and focus + window.createIframe = function(id, crossOrigin = false) { + return new Promise(resolve => { + const origin = crossOrigin ? CROSS_ORIGIN : MY_ORIGIN; + let iframe = document.createElement("iframe"); + iframe.id = id; + iframe.src = origin + window.location.pathname; + iframe.onload = () => resolve(iframe); + document.body.appendChild(iframe); + }); + } + + window.sendMessage = function(destWin, msg) { + return new Promise(resolve => { + let channel = new MessageChannel(); + channel.port1.onmessage = resolve; + destWin.postMessage(msg, "*", [channel.port2]); + }); + } + + window.onMessage = function(event) { + let canReply = event.ports && !!event.ports.length; + if(event.data === "open") { + window.openedWindow = window.open('https://example.com' + window.location.pathname); + if (canReply) event.ports[0].postMessage('opened'); + } else if(event.data === "openpopup") { + window.openedWindow = window.open('https://example.com' + window.location.pathname, 'test', 'top=0,height=1, width=300'); + if (canReply) event.ports[0].postMessage('popupopened'); + } else if(event.data === "focus") { + window.openedWindow.focus(); + if (canReply) event.ports[0].postMessage('focused'); + } else if(event.data === "elementfocus") { + document.querySelector("input").focus(); + if (canReply) event.ports[0].postMessage('elementfocused'); + } else if(event.data === "clicklink") { + synthesizeMouseAtCenter(document.querySelector("a"), {}); + if (canReply) event.ports[0].postMessage('linkclicked'); + } + } + window.addEventListener('message', window.onMessage); + </script> +</body> +</html> |