summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/permissions
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /browser/base/content/test/permissions
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/base/content/test/permissions')
-rw-r--r--browser/base/content/test/permissions/browser.ini41
-rw-r--r--browser/base/content/test/permissions/browser_autoplay_blocked.html14
-rw-r--r--browser/base/content/test/permissions/browser_autoplay_blocked.js357
-rw-r--r--browser/base/content/test/permissions/browser_autoplay_blocked_slow.sjs36
-rw-r--r--browser/base/content/test/permissions/browser_autoplay_js.html16
-rw-r--r--browser/base/content/test/permissions/browser_autoplay_muted.html14
-rw-r--r--browser/base/content/test/permissions/browser_canvas_fingerprinting_resistance.js383
-rw-r--r--browser/base/content/test/permissions/browser_canvas_rfp_exclusion.js194
-rw-r--r--browser/base/content/test/permissions/browser_permission_delegate_geo.js279
-rw-r--r--browser/base/content/test/permissions/browser_permissions.js569
-rw-r--r--browser/base/content/test/permissions/browser_permissions_delegate_vibrate.js46
-rw-r--r--browser/base/content/test/permissions/browser_permissions_handling_user_input.js99
-rw-r--r--browser/base/content/test/permissions/browser_permissions_postPrompt.js104
-rw-r--r--browser/base/content/test/permissions/browser_reservedkey.js312
-rw-r--r--browser/base/content/test/permissions/browser_site_scoped_permissions.js106
-rw-r--r--browser/base/content/test/permissions/browser_temporary_permissions.js118
-rw-r--r--browser/base/content/test/permissions/browser_temporary_permissions_expiry.js208
-rw-r--r--browser/base/content/test/permissions/browser_temporary_permissions_navigation.js239
-rw-r--r--browser/base/content/test/permissions/browser_temporary_permissions_tabs.js148
-rw-r--r--browser/base/content/test/permissions/dummy.js1
-rw-r--r--browser/base/content/test/permissions/empty.html8
-rw-r--r--browser/base/content/test/permissions/head.js28
-rw-r--r--browser/base/content/test/permissions/permissions.html49
-rw-r--r--browser/base/content/test/permissions/temporary_permissions_frame.html12
-rw-r--r--browser/base/content/test/permissions/temporary_permissions_subframe.html11
25 files changed, 3392 insertions, 0 deletions
diff --git a/browser/base/content/test/permissions/browser.ini b/browser/base/content/test/permissions/browser.ini
new file mode 100644
index 0000000000..216c0efb05
--- /dev/null
+++ b/browser/base/content/test/permissions/browser.ini
@@ -0,0 +1,41 @@
+[DEFAULT]
+support-files=
+ head.js
+ permissions.html
+ temporary_permissions_subframe.html
+ temporary_permissions_frame.html
+[browser_autoplay_blocked.js]
+support-files =
+ browser_autoplay_blocked.html
+ browser_autoplay_blocked_slow.sjs
+ browser_autoplay_js.html
+ browser_autoplay_muted.html
+ ../general/audio.ogg
+skip-if = true # Bug 1538602
+[browser_canvas_fingerprinting_resistance.js]
+skip-if =
+ debug
+ os == "linux" && asan # Bug 1522069
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_canvas_rfp_exclusion.js]
+[browser_permission_delegate_geo.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_permissions.js]
+[browser_permissions_delegate_vibrate.js]
+support-files=
+ empty.html
+[browser_permissions_handling_user_input.js]
+support-files=
+ dummy.js
+[browser_permissions_postPrompt.js]
+support-files=
+ dummy.js
+[browser_reservedkey.js]
+[browser_site_scoped_permissions.js]
+[browser_temporary_permissions.js]
+support-files =
+ ../webrtc/get_user_media.html
+[browser_temporary_permissions_expiry.js]
+[browser_temporary_permissions_navigation.js]
+[browser_temporary_permissions_tabs.js]
diff --git a/browser/base/content/test/permissions/browser_autoplay_blocked.html b/browser/base/content/test/permissions/browser_autoplay_blocked.html
new file mode 100644
index 0000000000..8c3b058890
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <audio autoplay="autoplay" >
+ <source src="audio.ogg" />
+ </audio>
+ </body>
+</html>
diff --git a/browser/base/content/test/permissions/browser_autoplay_blocked.js b/browser/base/content/test/permissions/browser_autoplay_blocked.js
new file mode 100644
index 0000000000..04b0316345
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.js
@@ -0,0 +1,357 @@
+/*
+ * Test that a blocked request to autoplay media is shown to the user
+ */
+
+const AUTOPLAY_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "browser_autoplay_blocked.html";
+
+const AUTOPLAY_JS_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "browser_autoplay_js.html";
+
+const SLOW_AUTOPLAY_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "browser_autoplay_blocked_slow.sjs";
+
+const MUTED_AUTOPLAY_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "browser_autoplay_muted.html";
+
+const EMPTY_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "empty.html";
+
+const AUTOPLAY_PREF = "media.autoplay.default";
+const AUTOPLAY_PERM = "autoplay-media";
+
+function autoplayBlockedIcon() {
+ return document.querySelector(
+ "#blocked-permissions-container " +
+ ".blocked-permission-icon.autoplay-media-icon"
+ );
+}
+
+function permissionListBlockedIcons() {
+ return document.querySelectorAll(
+ "image.permission-popup-permission-icon.blocked-permission-icon"
+ );
+}
+
+function sleep(ms) {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+async function blockedIconShown() {
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.is_visible(autoplayBlockedIcon());
+ }, "Blocked icon is shown");
+}
+
+async function blockedIconHidden() {
+ await TestUtils.waitForCondition(() => {
+ return BrowserTestUtils.is_hidden(autoplayBlockedIcon());
+ }, "Blocked icon is hidden");
+}
+
+function testPermListHasEntries(expectEntries) {
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let listEntryCount = permissionsList.querySelectorAll(
+ ".permission-popup-permission-item"
+ ).length;
+ if (expectEntries) {
+ ok(listEntryCount, "List of permissions is not empty");
+ return;
+ }
+ ok(!listEntryCount, "List of permissions is empty");
+}
+
+add_setup(async function () {
+ registerCleanupFunction(() => {
+ Services.perms.removeAll();
+ Services.prefs.clearUserPref(AUTOPLAY_PREF);
+ });
+});
+
+add_task(async function testMainViewVisible() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.ALLOWED);
+
+ await BrowserTestUtils.withNewTab(AUTOPLAY_PAGE, async function () {
+ ok(
+ BrowserTestUtils.is_hidden(autoplayBlockedIcon()),
+ "Blocked icon not shown"
+ );
+
+ await openPermissionPopup();
+ testPermListHasEntries(false);
+ await closePermissionPopup();
+ });
+
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
+
+ await BrowserTestUtils.withNewTab(AUTOPLAY_PAGE, async function (browser) {
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+
+ await blockedIconShown();
+
+ await openPermissionPopup();
+ testPermListHasEntries(true);
+
+ let labelText = SitePermissions.getPermissionLabel(AUTOPLAY_PERM);
+ let labels = permissionsList.querySelectorAll(
+ ".permission-popup-permission-label"
+ );
+ is(labels.length, 1, "One permission visible in main view");
+ is(labels[0].textContent, labelText, "Correct value");
+
+ let menulist = document.getElementById("permission-popup-menulist");
+ Assert.equal(menulist.label, "Block Audio");
+
+ await EventUtils.synthesizeMouseAtCenter(menulist, { type: "mousedown" });
+ await TestUtils.waitForCondition(() => {
+ return (
+ menulist.getElementsByTagName("menuitem")[0].label ===
+ "Allow Audio and Video"
+ );
+ });
+
+ let menuitem = menulist.getElementsByTagName("menuitem")[0];
+ Assert.equal(menuitem.getAttribute("label"), "Allow Audio and Video");
+
+ menuitem.click();
+ menulist.menupopup.hidePopup();
+ await closePermissionPopup();
+
+ let uri = Services.io.newURI(AUTOPLAY_PAGE);
+ let state = PermissionTestUtils.getPermissionObject(
+ uri,
+ AUTOPLAY_PERM
+ ).capability;
+ Assert.equal(state, Services.perms.ALLOW_ACTION);
+ });
+
+ Services.perms.removeAll();
+});
+
+add_task(async function testGloballyBlockedOnNewWindow() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
+
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ AUTOPLAY_PAGE
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ AUTOPLAY_PAGE
+ );
+ await blockedIconShown();
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(
+ principal,
+ AUTOPLAY_PERM,
+ tab.linkedBrowser
+ ),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ }
+ );
+
+ let promiseWin = BrowserTestUtils.waitForNewWindow();
+ gBrowser.replaceTabWithWindow(tab);
+ let win = await promiseWin;
+ tab = win.gBrowser.selectedTab;
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(
+ principal,
+ AUTOPLAY_PERM,
+ tab.linkedBrowser
+ ),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ }
+ );
+
+ SitePermissions.removeFromPrincipal(
+ principal,
+ AUTOPLAY_PERM,
+ tab.linkedBrowser
+ );
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function testBFCache() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
+
+ await BrowserTestUtils.withNewTab("about:home", async function (browser) {
+ BrowserTestUtils.loadURIString(browser, AUTOPLAY_PAGE);
+ await blockedIconShown();
+
+ gBrowser.goBack();
+ await blockedIconHidden();
+
+ // Not sure why using `gBrowser.goForward()` doesn't trigger document's
+ // visibility changes in some debug build on try server, which makes us not
+ // to receive the blocked event.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.history.forward();
+ });
+ await blockedIconShown();
+ });
+
+ Services.perms.removeAll();
+});
+
+add_task(async function testBlockedIconFromCORSIframe() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
+
+ await BrowserTestUtils.withNewTab(EMPTY_PAGE, async browser => {
+ const blockedIconShownPromise = blockedIconShown();
+ const CORS_AUTOPLAY_PAGE = AUTOPLAY_PAGE.replace(
+ "example.com",
+ "example.org"
+ );
+ info(`Load CORS autoplay on an iframe`);
+ await SpecialPowers.spawn(browser, [CORS_AUTOPLAY_PAGE], async url => {
+ const iframe = content.document.createElement("iframe");
+ iframe.src = url;
+ content.document.body.appendChild(iframe);
+ info("Wait until iframe finishes loading");
+ await new Promise(r => (iframe.onload = r));
+ });
+ await blockedIconShownPromise;
+ ok(true, "Blocked icon shown for the CORS autoplay iframe");
+ });
+
+ Services.perms.removeAll();
+});
+
+add_task(async function testChangingBlockingSettingDuringNavigation() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
+
+ await BrowserTestUtils.withNewTab("about:home", async function (browser) {
+ await blockedIconHidden();
+ BrowserTestUtils.loadURIString(browser, AUTOPLAY_PAGE);
+ await blockedIconShown();
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.ALLOWED);
+
+ gBrowser.goBack();
+ await blockedIconHidden();
+
+ gBrowser.goForward();
+
+ // Sleep here to prevent false positives, the icon gets shown with an
+ // async `GloballyAutoplayBlocked` event. The sleep gives it a little
+ // time for it to show otherwise there is a chance it passes before it
+ // would have shown.
+ await sleep(100);
+ ok(
+ BrowserTestUtils.is_hidden(autoplayBlockedIcon()),
+ "Blocked icon is hidden"
+ );
+ });
+
+ Services.perms.removeAll();
+});
+
+add_task(async function testSlowLoadingPage() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:home"
+ );
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ SLOW_AUTOPLAY_PAGE
+ );
+ await blockedIconShown();
+
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ // Wait until the blocked icon is hidden by switching tabs
+ await blockedIconHidden();
+ await BrowserTestUtils.switchTab(gBrowser, tab2);
+ await blockedIconShown();
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+
+ Services.perms.removeAll();
+});
+
+add_task(async function testBlockedAll() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED_ALL);
+
+ await BrowserTestUtils.withNewTab("about:home", async function (browser) {
+ await blockedIconHidden();
+ BrowserTestUtils.loadURIString(browser, MUTED_AUTOPLAY_PAGE);
+ await blockedIconShown();
+
+ await openPermissionPopup();
+
+ Assert.equal(
+ permissionListBlockedIcons().length,
+ 1,
+ "Blocked icon is shown"
+ );
+
+ let menulist = document.getElementById("permission-popup-menulist");
+ await EventUtils.synthesizeMouseAtCenter(menulist, { type: "mousedown" });
+ await TestUtils.waitForCondition(() => {
+ return (
+ menulist.getElementsByTagName("menuitem")[1].label === "Block Audio"
+ );
+ });
+
+ let menuitem = menulist.getElementsByTagName("menuitem")[0];
+ menuitem.click();
+ menulist.menupopup.hidePopup();
+ await closePermissionPopup();
+ gBrowser.reload();
+ await blockedIconHidden();
+ });
+ Services.perms.removeAll();
+});
+
+add_task(async function testMultiplePlayNotificationsFromJS() {
+ Services.prefs.setIntPref(AUTOPLAY_PREF, Ci.nsIAutoplay.BLOCKED);
+
+ await BrowserTestUtils.withNewTab("about:home", async function (browser) {
+ let count = 0;
+ browser.addEventListener("GloballyAutoplayBlocked", function () {
+ is(++count, 1, "Shouldn't get more than one autoplay blocked event");
+ });
+
+ await blockedIconHidden();
+
+ BrowserTestUtils.loadURIString(browser, AUTOPLAY_JS_PAGE);
+
+ await blockedIconShown();
+
+ // Sleep here a bit to ensure that multiple events don't arrive.
+ await sleep(100);
+
+ is(count, 1, "Shouldn't have got more events");
+ });
+
+ Services.perms.removeAll();
+});
diff --git a/browser/base/content/test/permissions/browser_autoplay_blocked_slow.sjs b/browser/base/content/test/permissions/browser_autoplay_blocked_slow.sjs
new file mode 100644
index 0000000000..12929760f7
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked_slow.sjs
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const DELAY_MS = 200;
+
+const AUTOPLAY_HTML = `<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <audio autoplay="autoplay" >
+ <source src="audio.ogg" />
+ </audio>
+ <script>
+ document.location.href = '#foo';
+ </script>
+ </body>
+</html>`;
+
+function handleRequest(req, resp) {
+ resp.processAsync();
+ resp.setHeader("Cache-Control", "no-cache", false);
+ resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ resp.write(AUTOPLAY_HTML);
+ timer.init(
+ () => {
+ resp.write("");
+ resp.finish();
+ },
+ DELAY_MS,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/browser/base/content/test/permissions/browser_autoplay_js.html b/browser/base/content/test/permissions/browser_autoplay_js.html
new file mode 100644
index 0000000000..9782487ee9
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_js.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<meta charset="utf8">
+<audio>
+ <source src="audio.ogg" />
+</audio>
+<script>
+onload = function() {
+ let audio = document.querySelector("audio");
+ for (let i = 0; i < 100; ++i) {
+ audio.play();
+ }
+};
+</script>
diff --git a/browser/base/content/test/permissions/browser_autoplay_muted.html b/browser/base/content/test/permissions/browser_autoplay_muted.html
new file mode 100644
index 0000000000..4f9d1ca846
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_muted.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <audio autoplay="autoplay" muted>
+ <source src="audio.ogg" />
+ </audio>
+ </body>
+</html>
diff --git a/browser/base/content/test/permissions/browser_canvas_fingerprinting_resistance.js b/browser/base/content/test/permissions/browser_canvas_fingerprinting_resistance.js
new file mode 100644
index 0000000000..dbb2d1ea32
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_canvas_fingerprinting_resistance.js
@@ -0,0 +1,383 @@
+/**
+ * When "privacy.resistFingerprinting" is set to true, user permission is
+ * required for canvas data extraction.
+ * This tests whether the site permission prompt for canvas data extraction
+ * works properly.
+ * When "privacy.resistFingerprinting.randomDataOnCanvasExtract" is true,
+ * canvas data extraction results in random data, and when it is false, canvas
+ * data extraction results in all-white data.
+ */
+"use strict";
+
+const kUrl = "https://example.com/";
+const kPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(kUrl),
+ {}
+);
+const kPermission = "canvas";
+
+function initTab() {
+ let contentWindow = content.wrappedJSObject;
+
+ let drawCanvas = (fillStyle, id) => {
+ let contentDocument = contentWindow.document;
+ let width = 64,
+ height = 64;
+ let canvas = contentDocument.createElement("canvas");
+ if (id) {
+ canvas.setAttribute("id", id);
+ }
+ canvas.setAttribute("width", width);
+ canvas.setAttribute("height", height);
+ contentDocument.body.appendChild(canvas);
+
+ let context = canvas.getContext("2d");
+ context.fillStyle = fillStyle;
+ context.fillRect(0, 0, width, height);
+
+ if (id) {
+ let button = contentDocument.createElement("button");
+ button.addEventListener("click", function () {
+ canvas.toDataURL();
+ });
+ button.setAttribute("id", "clickme");
+ button.innerHTML = "Click Me!";
+ contentDocument.body.appendChild(button);
+ }
+
+ return canvas;
+ };
+
+ let placeholder = drawCanvas("white");
+ contentWindow.kPlaceholderData = placeholder.toDataURL();
+ let canvas = drawCanvas("cyan", "canvas-id-canvas");
+ contentWindow.kPlacedData = canvas.toDataURL();
+ is(
+ canvas.toDataURL(),
+ contentWindow.kPlacedData,
+ "privacy.resistFingerprinting = false, canvas data == placed data"
+ );
+ isnot(
+ canvas.toDataURL(),
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = false, canvas data != placeholder data"
+ );
+}
+
+function enableResistFingerprinting(
+ randomDataOnCanvasExtract,
+ autoDeclineNoInput
+) {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ [
+ "privacy.resistFingerprinting.randomDataOnCanvasExtract",
+ randomDataOnCanvasExtract,
+ ],
+ [
+ "privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts",
+ autoDeclineNoInput,
+ ],
+ ],
+ });
+}
+
+function promisePopupShown() {
+ return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+}
+
+function promisePopupHidden() {
+ return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+}
+
+function extractCanvasData(randomDataOnCanvasExtract, grantPermission) {
+ let contentWindow = content.wrappedJSObject;
+ let canvas = contentWindow.document.getElementById("canvas-id-canvas");
+ let canvasData = canvas.toDataURL();
+ if (grantPermission) {
+ is(
+ canvasData,
+ contentWindow.kPlacedData,
+ "privacy.resistFingerprinting = true, permission granted, canvas data == placed data"
+ );
+ if (!randomDataOnCanvasExtract) {
+ isnot(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission granted, canvas data != placeholderdata"
+ );
+ }
+ } else if (grantPermission === false) {
+ isnot(
+ canvasData,
+ contentWindow.kPlacedData,
+ "privacy.resistFingerprinting = true, permission denied, canvas data != placed data"
+ );
+ if (!randomDataOnCanvasExtract) {
+ is(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission denied, canvas data == placeholderdata"
+ );
+ } else {
+ isnot(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, permission denied, canvas data != placeholderdata"
+ );
+ }
+ } else {
+ isnot(
+ canvasData,
+ contentWindow.kPlacedData,
+ "privacy.resistFingerprinting = true, requesting permission, canvas data != placed data"
+ );
+ if (!randomDataOnCanvasExtract) {
+ is(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, requesting permission, canvas data == placeholderdata"
+ );
+ } else {
+ isnot(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, requesting permission, canvas data != placeholderdata"
+ );
+ }
+ }
+}
+
+function triggerCommand(button) {
+ let notifications = PopupNotifications.panel.children;
+ let notification = notifications[0];
+ EventUtils.synthesizeMouseAtCenter(notification[button], {});
+}
+
+function triggerMainCommand() {
+ triggerCommand("button");
+}
+
+function triggerSecondaryCommand() {
+ triggerCommand("secondaryButton");
+}
+
+function testPermission() {
+ return Services.perms.testPermissionFromPrincipal(kPrincipal, kPermission);
+}
+
+async function withNewTabNoInput(
+ randomDataOnCanvasExtract,
+ grantPermission,
+ browser
+) {
+ await SpecialPowers.spawn(browser, [], initTab);
+ await enableResistFingerprinting(randomDataOnCanvasExtract, false);
+ let popupShown = promisePopupShown();
+ await SpecialPowers.spawn(
+ browser,
+ [randomDataOnCanvasExtract],
+ extractCanvasData
+ );
+ await popupShown;
+ let popupHidden = promisePopupHidden();
+ if (grantPermission) {
+ triggerMainCommand();
+ await popupHidden;
+ is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted");
+ } else {
+ triggerSecondaryCommand();
+ await popupHidden;
+ is(testPermission(), Services.perms.DENY_ACTION, "permission denied");
+ }
+ await SpecialPowers.spawn(
+ browser,
+ [randomDataOnCanvasExtract, grantPermission],
+ extractCanvasData
+ );
+ await SpecialPowers.popPrefEnv();
+}
+
+async function doTestNoInput(randomDataOnCanvasExtract, grantPermission) {
+ await BrowserTestUtils.withNewTab(
+ kUrl,
+ withNewTabNoInput.bind(null, randomDataOnCanvasExtract, grantPermission)
+ );
+ Services.perms.removeFromPrincipal(kPrincipal, kPermission);
+}
+
+// With auto-declining disabled (not the default)
+// Tests clicking "Don't Allow" button of the permission prompt.
+add_task(doTestNoInput.bind(null, true, false));
+add_task(doTestNoInput.bind(null, false, false));
+
+// Tests clicking "Allow" button of the permission prompt.
+add_task(doTestNoInput.bind(null, true, true));
+add_task(doTestNoInput.bind(null, false, true));
+
+async function withNewTabAutoBlockNoInput(randomDataOnCanvasExtract, browser) {
+ await SpecialPowers.spawn(browser, [], initTab);
+ await enableResistFingerprinting(randomDataOnCanvasExtract, true);
+
+ let noShowHandler = () => {
+ ok(false, "The popup notification should not show in this case.");
+ };
+ PopupNotifications.panel.addEventListener("popupshown", noShowHandler, {
+ once: true,
+ });
+
+ let promisePopupObserver = TestUtils.topicObserved(
+ "PopupNotifications-updateNotShowing"
+ );
+
+ // Try to extract canvas data without user inputs.
+ await SpecialPowers.spawn(
+ browser,
+ [randomDataOnCanvasExtract],
+ extractCanvasData
+ );
+
+ await promisePopupObserver;
+ info("There should be no popup shown on the panel.");
+
+ // Check that the icon of canvas permission is shown.
+ let canvasNotification = PopupNotifications.getNotification(
+ "canvas-permissions-prompt",
+ browser
+ );
+
+ is(
+ canvasNotification.anchorElement.getAttribute("showing"),
+ "true",
+ "The canvas permission icon is correctly shown."
+ );
+ PopupNotifications.panel.removeEventListener("popupshown", noShowHandler);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+async function doTestAutoBlockNoInput(randomDataOnCanvasExtract) {
+ await BrowserTestUtils.withNewTab(
+ kUrl,
+ withNewTabAutoBlockNoInput.bind(null, randomDataOnCanvasExtract)
+ );
+}
+
+add_task(doTestAutoBlockNoInput.bind(null, true));
+add_task(doTestAutoBlockNoInput.bind(null, false));
+
+function extractCanvasDataUserInput(
+ randomDataOnCanvasExtract,
+ grantPermission
+) {
+ let contentWindow = content.wrappedJSObject;
+ let canvas = contentWindow.document.getElementById("canvas-id-canvas");
+ let canvasData = canvas.toDataURL();
+ if (grantPermission) {
+ is(
+ canvasData,
+ contentWindow.kPlacedData,
+ "privacy.resistFingerprinting = true, permission granted, canvas data == placed data"
+ );
+ if (!randomDataOnCanvasExtract) {
+ isnot(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission granted, canvas data != placeholderdata"
+ );
+ }
+ } else if (grantPermission === false) {
+ isnot(
+ canvasData,
+ contentWindow.kPlacedData,
+ "privacy.resistFingerprinting = true, permission denied, canvas data != placed data"
+ );
+ if (!randomDataOnCanvasExtract) {
+ is(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, permission denied, canvas data == placeholderdata"
+ );
+ } else {
+ isnot(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, permission denied, canvas data != placeholderdata"
+ );
+ }
+ } else {
+ isnot(
+ canvasData,
+ contentWindow.kPlacedData,
+ "privacy.resistFingerprinting = true, requesting permission, canvas data != placed data"
+ );
+ if (!randomDataOnCanvasExtract) {
+ is(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = false, requesting permission, canvas data == placeholderdata"
+ );
+ } else {
+ isnot(
+ canvasData,
+ contentWindow.kPlaceholderData,
+ "privacy.resistFingerprinting = true and randomDataOnCanvasExtract = true, requesting permission, canvas data != placeholderdata"
+ );
+ }
+ }
+}
+
+async function withNewTabInput(
+ randomDataOnCanvasExtract,
+ grantPermission,
+ browser
+) {
+ await SpecialPowers.spawn(browser, [], initTab);
+ await enableResistFingerprinting(randomDataOnCanvasExtract, true);
+ let popupShown = promisePopupShown();
+ await SpecialPowers.spawn(browser, [], function (host) {
+ E10SUtils.wrapHandlingUserInput(content, true, function () {
+ var button = content.document.getElementById("clickme");
+ button.click();
+ });
+ });
+ await popupShown;
+ let popupHidden = promisePopupHidden();
+ if (grantPermission) {
+ triggerMainCommand();
+ await popupHidden;
+ is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted");
+ } else {
+ triggerSecondaryCommand();
+ await popupHidden;
+ is(testPermission(), Services.perms.DENY_ACTION, "permission denied");
+ }
+ await SpecialPowers.spawn(
+ browser,
+ [randomDataOnCanvasExtract, grantPermission],
+ extractCanvasDataUserInput
+ );
+ await SpecialPowers.popPrefEnv();
+}
+
+async function doTestInput(
+ randomDataOnCanvasExtract,
+ grantPermission,
+ autoDeclineNoInput
+) {
+ await BrowserTestUtils.withNewTab(
+ kUrl,
+ withNewTabInput.bind(null, randomDataOnCanvasExtract, grantPermission)
+ );
+ Services.perms.removeFromPrincipal(kPrincipal, kPermission);
+}
+
+// With auto-declining enabled (the default)
+// Tests clicking "Don't Allow" button of the permission prompt.
+add_task(doTestInput.bind(null, true, false));
+add_task(doTestInput.bind(null, false, false));
+
+// Tests clicking "Allow" button of the permission prompt.
+add_task(doTestInput.bind(null, true, true));
+add_task(doTestInput.bind(null, false, true));
diff --git a/browser/base/content/test/permissions/browser_canvas_rfp_exclusion.js b/browser/base/content/test/permissions/browser_canvas_rfp_exclusion.js
new file mode 100644
index 0000000000..61c9c5bf84
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_canvas_rfp_exclusion.js
@@ -0,0 +1,194 @@
+/* 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/.
+ *
+ * Adapted from browser_canvas_fingerprinting_resistance.js
+ */
+"use strict";
+
+const kUrl = "https://example.com/";
+var gPlacedData = false;
+
+function initTab(performReadbackTest) {
+ let contentWindow = content.wrappedJSObject;
+
+ let drawCanvas = (fillStyle, id) => {
+ let contentDocument = contentWindow.document;
+ let width = 64,
+ height = 64;
+ let canvas = contentDocument.createElement("canvas");
+ if (id) {
+ canvas.setAttribute("id", id);
+ }
+ canvas.setAttribute("width", width);
+ canvas.setAttribute("height", height);
+ contentDocument.body.appendChild(canvas);
+
+ let context = canvas.getContext("2d");
+ context.fillStyle = fillStyle;
+ context.fillRect(0, 0, width, height);
+ return canvas;
+ };
+
+ let canvas = drawCanvas("cyan", "canvas-id-canvas");
+
+ let placedData = canvas.toDataURL();
+ if (performReadbackTest) {
+ is(
+ canvas.toDataURL(),
+ placedData,
+ "Reading the placed data twice didn't match"
+ );
+ return placedData;
+ }
+ return undefined;
+}
+
+function disableResistFingerprinting() {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", false],
+ ["privacy.resistFingerprinting.pbmode", false],
+ ],
+ });
+}
+
+function enableResistFingerprinting(RfpNonPbmExclusion, RfpDomainExclusion) {
+ if (RfpNonPbmExclusion && RfpDomainExclusion) {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting.pbmode", true],
+ ["privacy.resistFingerprinting.exemptedDomains", "example.com"],
+ ],
+ });
+ } else if (RfpNonPbmExclusion) {
+ return SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting.pbmode", true]],
+ });
+ } else if (RfpDomainExclusion) {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ ["privacy.resistFingerprinting.exemptedDomains", "example.com"],
+ ],
+ });
+ }
+ return SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+}
+
+function extractCanvasData(
+ placedData,
+ isPbm,
+ RfpNonPbmExclusion,
+ RfpDomainExclusion
+) {
+ let contentWindow = content.wrappedJSObject;
+ let canvas = contentWindow.document.getElementById("canvas-id-canvas");
+ let canvasData = canvas.toDataURL();
+
+ if (RfpDomainExclusion) {
+ is(
+ canvasData,
+ placedData,
+ `A: RFP, domain exempted, canvas data == placed data (isPbm: ${isPbm}, RfpNonPbmExclusion: ${RfpNonPbmExclusion}, RfpDomainExclusion: ${RfpDomainExclusion})`
+ );
+ } else if (!isPbm && RfpNonPbmExclusion) {
+ is(
+ canvasData,
+ placedData,
+ `B: RFP, nonPBM exempted, not in PBM, canvas data == placed data (isPbm: ${isPbm}, RfpNonPbmExclusion: ${RfpNonPbmExclusion}, RfpDomainExclusion: ${RfpDomainExclusion})`
+ );
+ } else if (isPbm && RfpNonPbmExclusion) {
+ isnot(
+ canvasData,
+ placedData,
+ `C: RFP, nonPBM exempted, in PBM, canvas data != placed data (isPbm: ${isPbm}, RfpNonPbmExclusion: ${RfpNonPbmExclusion}, RfpDomainExclusion: ${RfpDomainExclusion})`
+ );
+ } else {
+ isnot(
+ canvasData,
+ placedData,
+ `D: RFP, domain not exempted, nonPBM not exempted, canvas data != placed data (isPbm: ${isPbm}, RfpNonPbmExclusion: ${RfpNonPbmExclusion}, RfpDomainExclusion: ${RfpDomainExclusion})`
+ );
+ }
+}
+
+async function populatePlacedData() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await disableResistFingerprinting();
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser: win.gBrowser,
+ url: kUrl,
+ },
+ async function () {
+ let browser = win.gBrowser.selectedBrowser;
+ gPlacedData = await SpecialPowers.spawn(
+ browser,
+ [/* performReadbackTest= */ true],
+ initTab
+ );
+ }
+ );
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+}
+
+async function rfpExclusionTestOnCanvas(
+ win,
+ placedData,
+ isPbm,
+ RfpNonPbmExclusion,
+ RfpDomainExclusion
+) {
+ let browser = win.gBrowser.selectedBrowser;
+ await SpecialPowers.spawn(
+ browser,
+ [/* performReadbackTest= */ false],
+ initTab
+ );
+ await SpecialPowers.spawn(
+ browser,
+ [placedData, isPbm, RfpNonPbmExclusion, RfpDomainExclusion],
+ extractCanvasData
+ );
+}
+
+async function testCanvasRfpExclusion(
+ isPbm,
+ RfpNonPbmExclusion,
+ RfpDomainExclusion
+) {
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: isPbm,
+ });
+ await enableResistFingerprinting(RfpNonPbmExclusion, RfpDomainExclusion);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser: win.gBrowser,
+ url: kUrl,
+ },
+ rfpExclusionTestOnCanvas.bind(
+ null,
+ win,
+ gPlacedData,
+ isPbm,
+ RfpNonPbmExclusion,
+ RfpDomainExclusion
+ )
+ );
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+}
+
+add_task(populatePlacedData.bind(null));
+add_task(testCanvasRfpExclusion.bind(null, false, false, false));
+add_task(testCanvasRfpExclusion.bind(null, false, false, true));
+add_task(testCanvasRfpExclusion.bind(null, false, true, false));
+add_task(testCanvasRfpExclusion.bind(null, false, true, true));
+add_task(testCanvasRfpExclusion.bind(null, true, false, false));
+add_task(testCanvasRfpExclusion.bind(null, true, false, true));
+add_task(testCanvasRfpExclusion.bind(null, true, true, false));
+add_task(testCanvasRfpExclusion.bind(null, true, true, true));
diff --git a/browser/base/content/test/permissions/browser_permission_delegate_geo.js b/browser/base/content/test/permissions/browser_permission_delegate_geo.js
new file mode 100644
index 0000000000..45e78d4519
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_permission_delegate_geo.js
@@ -0,0 +1,279 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ORIGIN = "https://example.com";
+const CROSS_SUBFRAME_PAGE =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
+ "temporary_permissions_subframe.html";
+
+const CROSS_FRAME_PAGE =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
+ "temporary_permissions_frame.html";
+
+const PromptResult = {
+ ALLOW: "allow",
+ DENY: "deny",
+ PROMPT: "prompt",
+};
+
+var Perms = Services.perms;
+var uri = NetUtil.newURI(ORIGIN);
+var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
+
+async function checkNotificationBothOrigins(
+ firstPartyOrigin,
+ thirdPartyOrigin
+) {
+ // Notification is shown, check label and deny to clean
+ let popuphidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+
+ let notification = PopupNotifications.panel.firstElementChild;
+ // Check the label of the notificaiton should be the first party
+ is(
+ PopupNotifications.getNotification("geolocation").options.name,
+ firstPartyOrigin,
+ "Use first party's origin"
+ );
+
+ // Check the second name of the notificaiton should be the third party
+ is(
+ PopupNotifications.getNotification("geolocation").options.secondName,
+ thirdPartyOrigin,
+ "Use third party's origin"
+ );
+
+ // Check remember checkbox is hidden
+ let checkbox = notification.checkbox;
+ ok(!!checkbox, "checkbox is present");
+ ok(checkbox.hidden, "checkbox is not visible");
+ ok(!checkbox.checked, "checkbox not checked");
+
+ EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+ await popuphidden;
+}
+
+async function checkGeolocation(browser, frameId, expect) {
+ let isPrompt = expect == PromptResult.PROMPT;
+ let waitForPrompt;
+ if (isPrompt) {
+ waitForPrompt = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ }
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ frameId, expect, isPrompt }],
+ async args => {
+ let frame = content.document.getElementById(args.frameId);
+
+ let waitForNoPrompt = new Promise(resolve => {
+ function onMessage(event) {
+ // Check the result right here because there's no notification
+ Assert.equal(
+ event.data,
+ args.expect,
+ "Correct expectation for third party"
+ );
+ content.window.removeEventListener("message", onMessage);
+ resolve();
+ }
+
+ if (!args.isPrompt) {
+ content.window.addEventListener("message", onMessage);
+ }
+ });
+
+ await content.SpecialPowers.spawn(frame, [], async () => {
+ const { E10SUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/E10SUtils.sys.mjs"
+ );
+
+ E10SUtils.wrapHandlingUserInput(this.content, true, function () {
+ let frameDoc = this.content.document;
+ frameDoc.getElementById("geo").click();
+ });
+ });
+
+ if (!args.isPrompt) {
+ await waitForNoPrompt;
+ }
+ }
+ );
+
+ if (isPrompt) {
+ await waitForPrompt;
+ }
+}
+
+add_setup(async function () {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ["permissions.delegation.enabled", true],
+ // This is the amount of time before the repeating
+ // NetworkGeolocationProvider timer is stopped.
+ // It needs to be less than 5000ms, or the timer will be
+ // reported as left behind by the test.
+ ["geo.timeout", 4000],
+ ],
+ },
+ r
+ );
+ });
+});
+
+// Test that temp blocked permissions in first party affect the third party
+// iframe.
+add_task(async function testUseTempPermissionsFirstParty() {
+ await BrowserTestUtils.withNewTab(
+ CROSS_SUBFRAME_PAGE,
+ async function (browser) {
+ SitePermissions.setForPrincipal(
+ principal,
+ "geo",
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ await checkGeolocation(browser, "frame", PromptResult.DENY);
+
+ SitePermissions.removeFromPrincipal(principal, "geo", browser);
+ }
+ );
+});
+
+// Test that persistent permissions in first party affect the third party
+// iframe.
+add_task(async function testUsePersistentPermissionsFirstParty() {
+ await BrowserTestUtils.withNewTab(
+ CROSS_SUBFRAME_PAGE,
+ async function (browser) {
+ async function checkPermission(aPermission, aExpect) {
+ PermissionTestUtils.add(uri, "geo", aPermission);
+ await checkGeolocation(browser, "frame", aExpect);
+
+ if (aExpect == PromptResult.PROMPT) {
+ // Notification is shown, check label and deny to clean
+ let popuphidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+
+ let notification = PopupNotifications.panel.firstElementChild;
+ // Check the label of the notificaiton should be the first party
+ is(
+ PopupNotifications.getNotification("geolocation").options.name,
+ uri.host,
+ "Use first party's origin"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+
+ await popuphidden;
+ SitePermissions.removeFromPrincipal(null, "geo", browser);
+ }
+
+ PermissionTestUtils.remove(uri, "geo");
+ }
+
+ await checkPermission(Perms.PROMPT_ACTION, PromptResult.PROMPT);
+ await checkPermission(Perms.DENY_ACTION, PromptResult.DENY);
+ await checkPermission(Perms.ALLOW_ACTION, PromptResult.ALLOW);
+ }
+ );
+});
+
+// Test that we do not prompt for maybe unsafe permission delegation if the
+// origin of the page is the original src origin.
+add_task(async function testPromptInMaybeUnsafePermissionDelegation() {
+ await BrowserTestUtils.withNewTab(
+ CROSS_SUBFRAME_PAGE,
+ async function (browser) {
+ // Persistent allow top level origin
+ PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);
+
+ await checkGeolocation(browser, "frameAllowsAll", PromptResult.ALLOW);
+
+ SitePermissions.removeFromPrincipal(null, "geo", browser);
+ PermissionTestUtils.remove(uri, "geo");
+ }
+ );
+});
+
+// Test that we should prompt if we are in unsafe permission delegation and
+// change location to origin which is not explicitly trusted. The prompt popup
+// should include both first and third party origin.
+add_task(async function testPromptChangeLocationUnsafePermissionDelegation() {
+ await BrowserTestUtils.withNewTab(
+ CROSS_SUBFRAME_PAGE,
+ async function (browser) {
+ // Persistent allow top level origin
+ PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);
+
+ let iframe = await SpecialPowers.spawn(browser, [], () => {
+ return content.document.getElementById("frameAllowsAll")
+ .browsingContext;
+ });
+
+ let otherURI =
+ "https://test1.example.com/browser/browser/base/content/test/permissions/permissions.html";
+ let loaded = BrowserTestUtils.browserLoaded(browser, true, otherURI);
+ await SpecialPowers.spawn(iframe, [otherURI], async function (_otherURI) {
+ content.location = _otherURI;
+ });
+ await loaded;
+
+ await checkGeolocation(browser, "frameAllowsAll", PromptResult.PROMPT);
+ await checkNotificationBothOrigins(uri.host, "test1.example.com");
+
+ SitePermissions.removeFromPrincipal(null, "geo", browser);
+ PermissionTestUtils.remove(uri, "geo");
+ }
+ );
+});
+
+// If we are in unsafe permission delegation and the origin is explicitly
+// trusted in ancestor chain. Do not need prompt
+add_task(async function testExplicitlyAllowedInChain() {
+ await BrowserTestUtils.withNewTab(CROSS_FRAME_PAGE, async function (browser) {
+ // Persistent allow top level origin
+ PermissionTestUtils.add(uri, "geo", Perms.ALLOW_ACTION);
+
+ let iframeAncestor = await SpecialPowers.spawn(browser, [], () => {
+ return content.document.getElementById("frameAncestor").browsingContext;
+ });
+
+ let iframe = await SpecialPowers.spawn(iframeAncestor, [], () => {
+ return content.document.getElementById("frameAllowsAll").browsingContext;
+ });
+
+ // Change location to check that we actually look at the ancestor chain
+ // instead of just considering the "same origin as src" rule.
+ let otherURI =
+ "https://test2.example.com/browser/browser/base/content/test/permissions/permissions.html";
+ let loaded = BrowserTestUtils.browserLoaded(browser, true, otherURI);
+ await SpecialPowers.spawn(iframe, [otherURI], async function (_otherURI) {
+ content.location = _otherURI;
+ });
+ await loaded;
+
+ await checkGeolocation(
+ iframeAncestor,
+ "frameAllowsAll",
+ PromptResult.ALLOW
+ );
+
+ PermissionTestUtils.remove(uri, "geo");
+ });
+});
diff --git a/browser/base/content/test/permissions/browser_permissions.js b/browser/base/content/test/permissions/browser_permissions.js
new file mode 100644
index 0000000000..0843ed9119
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_permissions.js
@@ -0,0 +1,569 @@
+/*
+ * Test the Permissions section in the Control Center.
+ */
+
+const PERMISSIONS_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "permissions.html";
+
+function testPermListHasEntries(expectEntries) {
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let listEntryCount = permissionsList.querySelectorAll(
+ ".permission-popup-permission-item"
+ ).length;
+ if (expectEntries) {
+ ok(listEntryCount, "List of permissions is not empty");
+ return;
+ }
+ ok(!listEntryCount, "List of permissions is empty");
+}
+
+add_task(async function testMainViewVisible() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function () {
+ await openPermissionPopup();
+
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ testPermListHasEntries(false);
+
+ await closePermissionPopup();
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "camera",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await openPermissionPopup();
+
+ testPermListHasEntries(true);
+
+ let labelText = SitePermissions.getPermissionLabel("camera");
+ let labels = permissionsList.querySelectorAll(
+ ".permission-popup-permission-label"
+ );
+ is(labels.length, 1, "One permission visible in main view");
+ is(labels[0].innerHTML, labelText, "Correct value");
+
+ let img = permissionsList.querySelector(
+ "image.permission-popup-permission-icon"
+ );
+ ok(img, "There is an image for the permissions");
+ ok(img.classList.contains("camera-icon"), "proper class is in image class");
+
+ await closePermissionPopup();
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "camera");
+
+ await openPermissionPopup();
+
+ testPermListHasEntries(false);
+
+ await closePermissionPopup();
+ });
+});
+
+add_task(async function testIdentityIcon() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, function () {
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "geo",
+ Services.perms.ALLOW_ACTION
+ );
+
+ ok(
+ gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-box signals granted permissions"
+ );
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "geo");
+
+ ok(
+ !gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-box doesn't signal granted permissions"
+ );
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "not-a-site-permission",
+ Services.perms.ALLOW_ACTION
+ );
+
+ ok(
+ !gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-box doesn't signal granted permissions"
+ );
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_SESSION
+ );
+
+ ok(
+ gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-box signals granted permissions"
+ );
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "cookie");
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_DENY
+ );
+
+ ok(
+ gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-box signals granted permissions"
+ );
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_DEFAULT
+ );
+
+ ok(
+ !gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-box doesn't signal granted permissions"
+ );
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "geo");
+ PermissionTestUtils.remove(gBrowser.currentURI, "not-a-site-permission");
+ PermissionTestUtils.remove(gBrowser.currentURI, "cookie");
+ });
+});
+
+add_task(async function testCancelPermission() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function () {
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "geo",
+ Services.perms.ALLOW_ACTION
+ );
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "camera",
+ Services.perms.DENY_ACTION
+ );
+
+ await openPermissionPopup();
+
+ testPermListHasEntries(true);
+
+ permissionsList
+ .querySelector(".permission-popup-permission-remove-button")
+ .click();
+
+ is(
+ permissionsList.querySelectorAll(".permission-popup-permission-label")
+ .length,
+ 1,
+ "First permission should be removed"
+ );
+
+ permissionsList
+ .querySelector(".permission-popup-permission-remove-button")
+ .click();
+
+ is(
+ permissionsList.querySelectorAll(".permission-popup-permission-label")
+ .length,
+ 0,
+ "Second permission should be removed"
+ );
+
+ await closePermissionPopup();
+ });
+});
+
+add_task(async function testPermissionHints() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function (browser) {
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let reloadHint = document.getElementById(
+ "permission-popup-permission-reload-hint"
+ );
+
+ await openPermissionPopup();
+
+ ok(BrowserTestUtils.is_hidden(reloadHint), "Reload hint is hidden");
+
+ await closePermissionPopup();
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "geo",
+ Services.perms.ALLOW_ACTION
+ );
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "camera",
+ Services.perms.DENY_ACTION
+ );
+
+ await openPermissionPopup();
+
+ ok(BrowserTestUtils.is_hidden(reloadHint), "Reload hint is hidden");
+
+ let cancelButtons = permissionsList.querySelectorAll(
+ ".permission-popup-permission-remove-button"
+ );
+ PermissionTestUtils.remove(gBrowser.currentURI, "camera");
+
+ cancelButtons[0].click();
+ ok(!BrowserTestUtils.is_hidden(reloadHint), "Reload hint is visible");
+
+ cancelButtons[1].click();
+ ok(!BrowserTestUtils.is_hidden(reloadHint), "Reload hint is visible");
+
+ await closePermissionPopup();
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, PERMISSIONS_PAGE);
+ await loaded;
+ await openPermissionPopup();
+
+ ok(
+ BrowserTestUtils.is_hidden(reloadHint),
+ "Reload hint is hidden after reloading"
+ );
+
+ await closePermissionPopup();
+ });
+});
+
+add_task(async function testPermissionIcons() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, function () {
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "camera",
+ Services.perms.ALLOW_ACTION
+ );
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "geo",
+ Services.perms.DENY_ACTION
+ );
+
+ let geoIcon = gPermissionPanel._identityPermissionBox.querySelector(
+ ".blocked-permission-icon[data-permission-id='geo']"
+ );
+ ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
+
+ let cameraIcon = gPermissionPanel._identityPermissionBox.querySelector(
+ ".blocked-permission-icon[data-permission-id='camera']"
+ );
+ ok(
+ !cameraIcon.hasAttribute("showing"),
+ "allowed permission icon is not shown"
+ );
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "geo");
+
+ ok(
+ !geoIcon.hasAttribute("showing"),
+ "blocked permission icon is not shown after reset"
+ );
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "camera");
+ });
+});
+
+add_task(async function testPermissionShortcuts() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function (browser) {
+ browser.focus();
+
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ { set: [["permissions.default.shortcuts", 0]] },
+ r
+ );
+ });
+
+ async function tryKey(desc, expectedValue) {
+ await EventUtils.synthesizeAndWaitKey("c", { accelKey: true });
+ let result = await SpecialPowers.spawn(browser, [], function () {
+ return {
+ keydowns: content.wrappedJSObject.gKeyDowns,
+ keypresses: content.wrappedJSObject.gKeyPresses,
+ };
+ });
+ is(
+ result.keydowns,
+ expectedValue,
+ "keydown event was fired or not fired as expected, " + desc
+ );
+ is(
+ result.keypresses,
+ 0,
+ "keypress event shouldn't be fired for shortcut key, " + desc
+ );
+ }
+
+ await tryKey("pressed with default permissions", 1);
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "shortcuts",
+ Services.perms.DENY_ACTION
+ );
+ await tryKey("pressed when site blocked", 1);
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "shortcuts",
+ PermissionTestUtils.ALLOW
+ );
+ await tryKey("pressed when site allowed", 2);
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "shortcuts");
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ { set: [["permissions.default.shortcuts", 2]] },
+ r
+ );
+ });
+
+ await tryKey("pressed when globally blocked", 2);
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "shortcuts",
+ Services.perms.ALLOW_ACTION
+ );
+ await tryKey("pressed when globally blocked but site allowed", 3);
+
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "shortcuts",
+ Services.perms.DENY_ACTION
+ );
+ await tryKey("pressed when globally blocked and site blocked", 3);
+
+ PermissionTestUtils.remove(gBrowser.currentURI, "shortcuts");
+ });
+});
+
+// Test the control center UI when policy permissions are set.
+add_task(async function testPolicyPermission() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.disable_open_during_load", true]],
+ });
+
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ PermissionTestUtils.add(
+ gBrowser.currentURI,
+ "popup",
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_POLICY
+ );
+
+ await openPermissionPopup();
+
+ // Check if the icon, nameLabel and stateLabel are visible.
+ let img, labelText, labels;
+
+ img = permissionsList.querySelector(
+ "image.permission-popup-permission-icon"
+ );
+ ok(img, "There is an image for the popup permission");
+ ok(img.classList.contains("popup-icon"), "proper class is in image class");
+
+ labelText = SitePermissions.getPermissionLabel("popup");
+ labels = permissionsList.querySelectorAll(
+ ".permission-popup-permission-label"
+ );
+ is(labels.length, 1, "One permission visible in main view");
+ is(labels[0].innerHTML, labelText, "Correct name label value");
+
+ labelText = SitePermissions.getCurrentStateLabel(
+ SitePermissions.ALLOW,
+ SitePermissions.SCOPE_POLICY
+ );
+ labels = permissionsList.querySelectorAll(
+ ".permission-popup-permission-state-label"
+ );
+ is(labels[0].innerHTML, labelText, "Correct state label value");
+
+ // Check if the menulist and the remove button are hidden.
+ // The menulist is specific to the "popup" permission.
+ let menulist = document.getElementById("permission-popup-menulist");
+ ok(menulist == null, "The popup permission menulist is not visible");
+
+ let removeButton = permissionsList.querySelector(
+ ".permission-popup-permission-remove-button"
+ );
+ ok(removeButton == null, "The permission remove button is not visible");
+
+ Services.perms.removeAll();
+ await closePermissionPopup();
+ });
+});
+
+add_task(async function testHiddenAfterRefresh() {
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function (browser) {
+ ok(
+ BrowserTestUtils.is_hidden(gPermissionPanel._permissionPopup),
+ "Popup is hidden"
+ );
+
+ await openPermissionPopup();
+
+ ok(
+ !BrowserTestUtils.is_hidden(gPermissionPanel._permissionPopup),
+ "Popup is shown"
+ );
+
+ let reloaded = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ PERMISSIONS_PAGE
+ );
+ EventUtils.synthesizeKey("VK_F5", {}, browser.ownerGlobal);
+ await reloaded;
+
+ ok(
+ BrowserTestUtils.is_hidden(gPermissionPanel._permissionPopup),
+ "Popup is hidden"
+ );
+ });
+});
+
+add_task(async function test3rdPartyStoragePermission() {
+ // 3rdPartyStorage permissions are listed under an anchor container - test
+ // that this works correctly, i.e. the permission items are added to the
+ // anchor when relevant, and other permission items are added to the default
+ // anchor, and adding/removing permissions preserves this behavior correctly.
+
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function (browser) {
+ await openPermissionPopup();
+
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let storagePermissionAnchor = permissionsList.querySelector(
+ `.permission-popup-permission-list-anchor[anchorfor="3rdPartyStorage"]`
+ );
+
+ testPermListHasEntries(false);
+
+ ok(
+ BrowserTestUtils.is_hidden(storagePermissionAnchor.firstElementChild),
+ "Anchor header is hidden"
+ );
+
+ await closePermissionPopup();
+
+ let storagePermissionID = "3rdPartyStorage^example2.com";
+ PermissionTestUtils.add(
+ browser.currentURI,
+ storagePermissionID,
+ Services.perms.ALLOW_ACTION
+ );
+
+ await openPermissionPopup();
+
+ testPermListHasEntries(true);
+ ok(
+ BrowserTestUtils.is_visible(storagePermissionAnchor.firstElementChild),
+ "Anchor header is visible"
+ );
+
+ let labelText = SitePermissions.getPermissionLabel(storagePermissionID);
+ let labels = storagePermissionAnchor.querySelectorAll(
+ ".permission-popup-permission-label"
+ );
+ is(labels.length, 1, "One permission visible in 3rdPartyStorage anchor");
+ is(
+ labels[0].getAttribute("value"),
+ labelText,
+ "Permission label has the correct value"
+ );
+
+ await closePermissionPopup();
+
+ PermissionTestUtils.add(
+ browser.currentURI,
+ "camera",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await openPermissionPopup();
+
+ testPermListHasEntries(true);
+ ok(
+ BrowserTestUtils.is_visible(storagePermissionAnchor.firstElementChild),
+ "Anchor header is visible"
+ );
+
+ labels = permissionsList.querySelectorAll(
+ ".permission-popup-permission-label"
+ );
+ is(labels.length, 2, "Two permissions visible in main view");
+ labels = storagePermissionAnchor.querySelectorAll(
+ ".permission-popup-permission-label"
+ );
+ is(labels.length, 1, "One permission visible in 3rdPartyStorage anchor");
+
+ storagePermissionAnchor
+ .querySelector(".permission-popup-permission-remove-button")
+ .click();
+ is(
+ storagePermissionAnchor.querySelectorAll(
+ ".permission-popup-permission-label"
+ ).length,
+ 0,
+ "Permission item should be removed"
+ );
+ is(
+ PermissionTestUtils.testPermission(
+ browser.currentURI,
+ storagePermissionID
+ ),
+ SitePermissions.UNKNOWN,
+ "Permission removed from permission manager"
+ );
+
+ await closePermissionPopup();
+
+ await openPermissionPopup();
+
+ testPermListHasEntries(true);
+ ok(
+ BrowserTestUtils.is_hidden(storagePermissionAnchor.firstElementChild),
+ "Anchor header is hidden"
+ );
+
+ labels = permissionsList.querySelectorAll(
+ ".permission-popup-permission-label"
+ );
+ is(labels.length, 1, "One permission visible in main view");
+
+ await closePermissionPopup();
+
+ PermissionTestUtils.remove(browser.currentURI, "camera");
+
+ await openPermissionPopup();
+
+ testPermListHasEntries(false);
+ ok(
+ BrowserTestUtils.is_hidden(storagePermissionAnchor.firstElementChild),
+ "Anchor header is hidden"
+ );
+
+ await closePermissionPopup();
+ });
+});
diff --git a/browser/base/content/test/permissions/browser_permissions_delegate_vibrate.js b/browser/base/content/test/permissions/browser_permissions_delegate_vibrate.js
new file mode 100644
index 0000000000..53be5cc175
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_permissions_delegate_vibrate.js
@@ -0,0 +1,46 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TEST_PAGE =
+ "https://example.com/browser/browser/base/content/test/permissions/empty.html";
+
+add_task(async function testNoPermissionPrompt() {
+ info("Creating tab");
+ await BrowserTestUtils.withNewTab(TEST_PAGE, async function (browser) {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["permissions.delegation.enabled", true],
+ ["dom.vibrator.enabled", true],
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+
+ await ContentTask.spawn(browser, null, async function () {
+ let frame = content.document.createElement("iframe");
+ // Cross origin src
+ frame.src =
+ "https://example.org/browser/browser/base/content/test/permissions/empty.html";
+ await new Promise(resolve => {
+ frame.addEventListener("load", () => {
+ resolve();
+ });
+ content.document.body.appendChild(frame);
+ });
+
+ await content.SpecialPowers.spawn(frame, [], async function () {
+ // Request a permission.
+ let result = this.content.navigator.vibrate([100, 100]);
+ Assert.equal(result, false, "navigator.vibrate has been denied");
+ });
+ content.document.body.removeChild(frame);
+ });
+ });
+});
diff --git a/browser/base/content/test/permissions/browser_permissions_handling_user_input.js b/browser/base/content/test/permissions/browser_permissions_handling_user_input.js
new file mode 100644
index 0000000000..94b69c4998
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_permissions_handling_user_input.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ORIGIN = "https://example.com";
+const PERMISSIONS_PAGE =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
+ "permissions.html";
+
+function assertShown(task) {
+ return BrowserTestUtils.withNewTab(
+ PERMISSIONS_PAGE,
+ async function (browser) {
+ let popupshown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ await SpecialPowers.spawn(browser, [], task);
+
+ await popupshown;
+
+ ok(true, "Notification permission prompt was shown");
+ }
+ );
+}
+
+function assertNotShown(task) {
+ return BrowserTestUtils.withNewTab(
+ PERMISSIONS_PAGE,
+ async function (browser) {
+ let popupshown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ await SpecialPowers.spawn(browser, [], task);
+
+ let sawPrompt = await Promise.race([
+ popupshown.then(() => true),
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ new Promise(c => setTimeout(() => c(false), 1000)),
+ ]);
+
+ is(sawPrompt, false, "Notification permission prompt was not shown");
+ }
+ );
+}
+
+// Tests that notification permissions are automatically denied without user interaction.
+add_task(async function testNotificationPermission() {
+ Services.prefs.setBoolPref(
+ "dom.webnotifications.requireuserinteraction",
+ true
+ );
+
+ // First test that when user interaction is required, requests
+ // with user interaction will show the permission prompt.
+
+ await assertShown(function () {
+ content.document.notifyUserGestureActivation();
+ content.document.getElementById("desktop-notification").click();
+ });
+
+ await assertShown(function () {
+ content.document.notifyUserGestureActivation();
+ content.document.getElementById("push").click();
+ });
+
+ // Now test that requests without user interaction will fail.
+
+ await assertNotShown(function () {
+ content.postMessage("push", "*");
+ });
+
+ await assertNotShown(async function () {
+ let response = await content.Notification.requestPermission();
+ is(response, "default", "The request was automatically denied");
+ });
+
+ Services.prefs.setBoolPref(
+ "dom.webnotifications.requireuserinteraction",
+ false
+ );
+
+ // Finally test that those requests will show a prompt again
+ // if the pref has been set to false.
+
+ await assertShown(function () {
+ content.postMessage("push", "*");
+ });
+
+ await assertShown(function () {
+ content.Notification.requestPermission();
+ });
+
+ Services.prefs.clearUserPref("dom.webnotifications.requireuserinteraction");
+});
diff --git a/browser/base/content/test/permissions/browser_permissions_postPrompt.js b/browser/base/content/test/permissions/browser_permissions_postPrompt.js
new file mode 100644
index 0000000000..8434f1fbb3
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_permissions_postPrompt.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ORIGIN = "https://example.com";
+const PERMISSIONS_PAGE =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
+ "permissions.html";
+
+function testPostPrompt(task) {
+ let uri = Services.io.newURI(PERMISSIONS_PAGE);
+ return BrowserTestUtils.withNewTab(
+ PERMISSIONS_PAGE,
+ async function (browser) {
+ let icon = document.getElementById("web-notifications-notification-icon");
+ ok(
+ !BrowserTestUtils.is_visible(icon),
+ "notifications icon is not visible at first"
+ );
+
+ await SpecialPowers.spawn(browser, [], task);
+
+ await TestUtils.waitForCondition(
+ () => BrowserTestUtils.is_visible(icon),
+ "notifications icon is visible"
+ );
+ ok(
+ !PopupNotifications.panel.hasAttribute("panelopen"),
+ "only the icon is showing, the panel is not open"
+ );
+
+ let popupshown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+ icon.click();
+ await popupshown;
+
+ ok(true, "Notification permission prompt was shown");
+
+ let notification = PopupNotifications.panel.firstElementChild;
+ EventUtils.synthesizeMouseAtCenter(notification.button, {});
+
+ is(
+ PermissionTestUtils.testPermission(uri, "desktop-notification"),
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ "User can override the default deny by using the prompt"
+ );
+
+ PermissionTestUtils.remove(uri, "desktop-notification");
+ }
+ );
+}
+
+add_task(async function testNotificationPermission() {
+ Services.prefs.setBoolPref(
+ "dom.webnotifications.requireuserinteraction",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "permissions.desktop-notification.postPrompt.enabled",
+ true
+ );
+
+ Services.prefs.setIntPref(
+ "permissions.default.desktop-notification",
+ Ci.nsIPermissionManager.DENY_ACTION
+ );
+
+ // First test that all requests (even with user interaction) will cause a post-prompt
+ // if the global default is "deny".
+
+ await testPostPrompt(function () {
+ E10SUtils.wrapHandlingUserInput(content, true, function () {
+ content.document.getElementById("desktop-notification").click();
+ });
+ });
+
+ await testPostPrompt(function () {
+ E10SUtils.wrapHandlingUserInput(content, true, function () {
+ content.document.getElementById("push").click();
+ });
+ });
+
+ Services.prefs.clearUserPref("permissions.default.desktop-notification");
+
+ // Now test that requests without user interaction will post-prompt when the
+ // user interaction requirement is set.
+
+ await testPostPrompt(function () {
+ content.postMessage("push", "*");
+ });
+
+ await testPostPrompt(async function () {
+ let response = await content.Notification.requestPermission();
+ is(response, "default", "The request was automatically denied");
+ });
+
+ Services.prefs.clearUserPref("dom.webnotifications.requireuserinteraction");
+ Services.prefs.clearUserPref(
+ "permissions.desktop-notification.postPrompt.enabled"
+ );
+});
diff --git a/browser/base/content/test/permissions/browser_reservedkey.js b/browser/base/content/test/permissions/browser_reservedkey.js
new file mode 100644
index 0000000000..c8eb0ab6c6
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_reservedkey.js
@@ -0,0 +1,312 @@
+add_task(async function test_reserved_shortcuts() {
+ let keyset = document.createXULElement("keyset");
+ let key1 = document.createXULElement("key");
+ key1.setAttribute("id", "kt_reserved");
+ key1.setAttribute("modifiers", "shift");
+ key1.setAttribute("key", "O");
+ key1.setAttribute("reserved", "true");
+ key1.setAttribute("count", "0");
+ key1.addEventListener("command", () => {
+ let attribute = key1.getAttribute("count");
+ key1.setAttribute("count", Number(attribute) + 1);
+ });
+
+ let key2 = document.createXULElement("key");
+ key2.setAttribute("id", "kt_notreserved");
+ key2.setAttribute("modifiers", "shift");
+ key2.setAttribute("key", "P");
+ key2.setAttribute("reserved", "false");
+ key2.setAttribute("count", "0");
+ key2.addEventListener("command", () => {
+ let attribute = key2.getAttribute("count");
+ key2.setAttribute("count", Number(attribute) + 1);
+ });
+
+ let key3 = document.createXULElement("key");
+ key3.setAttribute("id", "kt_reserveddefault");
+ key3.setAttribute("modifiers", "shift");
+ key3.setAttribute("key", "Q");
+ key3.setAttribute("count", "0");
+ key3.addEventListener("command", () => {
+ let attribute = key3.getAttribute("count");
+ key3.setAttribute("count", Number(attribute) + 1);
+ });
+
+ keyset.appendChild(key1);
+ keyset.appendChild(key2);
+ keyset.appendChild(key3);
+ let container = document.createXULElement("box");
+ container.appendChild(keyset);
+ document.documentElement.appendChild(container);
+
+ const pageUrl =
+ "data:text/html,<body onload='document.body.firstElementChild.focus();'><div onkeydown='event.preventDefault();' tabindex=0>Test</div></body>";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ EventUtils.sendString("OPQ");
+
+ is(
+ document.getElementById("kt_reserved").getAttribute("count"),
+ "1",
+ "reserved='true' with preference off"
+ );
+ is(
+ document.getElementById("kt_notreserved").getAttribute("count"),
+ "0",
+ "reserved='false' with preference off"
+ );
+ is(
+ document.getElementById("kt_reserveddefault").getAttribute("count"),
+ "0",
+ "default reserved with preference off"
+ );
+
+ // Now try with reserved shortcut key handling enabled.
+ await new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { set: [["permissions.default.shortcuts", 2]] },
+ resolve
+ );
+ });
+
+ EventUtils.sendString("OPQ");
+
+ is(
+ document.getElementById("kt_reserved").getAttribute("count"),
+ "2",
+ "reserved='true' with preference on"
+ );
+ is(
+ document.getElementById("kt_notreserved").getAttribute("count"),
+ "0",
+ "reserved='false' with preference on"
+ );
+ is(
+ document.getElementById("kt_reserveddefault").getAttribute("count"),
+ "1",
+ "default reserved with preference on"
+ );
+
+ document.documentElement.removeChild(container);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// This test checks that Alt+<key> and F10 cannot be blocked when the preference is set.
+if (!navigator.platform.includes("Mac")) {
+ add_task(async function test_accesskeys_menus() {
+ await new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { set: [["permissions.default.shortcuts", 2]] },
+ resolve
+ );
+ });
+
+ const uri =
+ 'data:text/html,<body onkeydown=\'if (event.key == "H" || event.key == "F10") event.preventDefault();\'>';
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri);
+
+ // Pressing Alt+H should open the Help menu.
+ let helpPopup = document.getElementById("menu_HelpPopup");
+ let popupShown = BrowserTestUtils.waitForEvent(helpPopup, "popupshown");
+ EventUtils.synthesizeKey("KEY_Alt", { type: "keydown" });
+ EventUtils.synthesizeKey("h", { altKey: true });
+ EventUtils.synthesizeKey("KEY_Alt", { type: "keyup" });
+ await popupShown;
+
+ ok(true, "Help menu opened");
+
+ let popupHidden = BrowserTestUtils.waitForEvent(helpPopup, "popuphidden");
+ helpPopup.hidePopup();
+ await popupHidden;
+
+ // Pressing F10 should focus the menubar. On Linux, the file menu should open, but on Windows,
+ // pressing Down will open the file menu.
+ let menubar = document.getElementById("main-menubar");
+ let menubarActive = BrowserTestUtils.waitForEvent(
+ menubar,
+ "DOMMenuBarActive"
+ );
+ EventUtils.synthesizeKey("KEY_F10");
+ await menubarActive;
+
+ let filePopup = document.getElementById("menu_FilePopup");
+ popupShown = BrowserTestUtils.waitForEvent(filePopup, "popupshown");
+ if (navigator.platform.includes("Win")) {
+ EventUtils.synthesizeKey("KEY_ArrowDown");
+ }
+ await popupShown;
+
+ ok(true, "File menu opened");
+
+ popupHidden = BrowserTestUtils.waitForEvent(filePopup, "popuphidden");
+ filePopup.hidePopup();
+ await popupHidden;
+
+ BrowserTestUtils.removeTab(tab1);
+ });
+}
+
+// There is a <key> element for Backspace and delete with reserved="false",
+// so make sure that it is not treated as a blocked shortcut key.
+add_task(async function test_backspace_delete() {
+ await new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { set: [["permissions.default.shortcuts", 2]] },
+ resolve
+ );
+ });
+
+ // The input field is autofocused. If this test fails, backspace can go back
+ // in history so cancel the beforeunload event and adjust the field to make the test fail.
+ const uri =
+ 'data:text/html,<body onbeforeunload=\'document.getElementById("field").value = "failed";\'>' +
+ "<input id='field' value='something'></body>";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.document.getElementById("field").focus();
+
+ // Add a promise that resolves when the backspace key gets received
+ // so we can ensure the key gets received before checking the result.
+ content.keysPromise = new Promise(resolve => {
+ content.addEventListener("keyup", event => {
+ if (event.code == "Backspace") {
+ resolve(content.document.getElementById("field").value);
+ }
+ });
+ });
+ });
+
+ // Move the caret so backspace will delete the first character.
+ EventUtils.synthesizeKey("KEY_ArrowRight", {});
+ EventUtils.synthesizeKey("KEY_Backspace", {});
+
+ let fieldValue = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ return content.keysPromise;
+ }
+ );
+ is(fieldValue, "omething", "backspace not prevented");
+
+ // now do the same thing for the delete key:
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.document.getElementById("field").focus();
+
+ // Add a promise that resolves when the backspace key gets received
+ // so we can ensure the key gets received before checking the result.
+ content.keysPromise = new Promise(resolve => {
+ content.addEventListener("keyup", event => {
+ if (event.code == "Delete") {
+ resolve(content.document.getElementById("field").value);
+ }
+ });
+ });
+ });
+
+ // Move the caret so backspace will delete the first character.
+ EventUtils.synthesizeKey("KEY_Delete", {});
+
+ fieldValue = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ return content.keysPromise;
+ }
+ );
+ is(fieldValue, "mething", "delete not prevented");
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+// TODO: Make this to run on Windows too to have automated tests also there.
+if (
+ navigator.platform.includes("Mac") ||
+ navigator.platform.includes("Linux")
+) {
+ add_task(
+ async function test_reserved_shortcuts_conflict_with_user_settings() {
+ await new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { set: [["test.events.async.enabled", true]] },
+ resolve
+ );
+ });
+
+ const keyset = document.createXULElement("keyset");
+ const key = document.createXULElement("key");
+ key.setAttribute("id", "conflict_with_known_native_key_binding");
+ if (navigator.platform.includes("Mac")) {
+ // Select to end of the paragraph
+ key.setAttribute("modifiers", "ctrl,shift");
+ key.setAttribute("key", "E");
+ } else {
+ // Select All
+ key.setAttribute("modifiers", "ctrl");
+ key.setAttribute("key", "a");
+ }
+ key.setAttribute("reserved", "true");
+ key.setAttribute("count", "0");
+ key.addEventListener("command", () => {
+ const attribute = key.getAttribute("count");
+ key.setAttribute("count", Number(attribute) + 1);
+ });
+
+ keyset.appendChild(key);
+ const container = document.createXULElement("box");
+ container.appendChild(keyset);
+ document.documentElement.appendChild(container);
+
+ const pageUrl =
+ "data:text/html,<body onload='document.body.firstChild.focus(); getSelection().collapse(document.body.firstChild, 0)'><div contenteditable>Test</div></body>";
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ pageUrl
+ );
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [key.getAttribute("key")],
+ async function (aExpectedKeyValue) {
+ content.promiseTestResult = new Promise(resolve => {
+ content.addEventListener("keyup", event => {
+ if (event.key.toLowerCase() == aExpectedKeyValue.toLowerCase()) {
+ resolve(content.getSelection().getRangeAt(0).toString());
+ }
+ });
+ });
+ }
+ );
+
+ EventUtils.synthesizeKey(key.getAttribute("key"), {
+ ctrlKey: key.getAttribute("modifiers").includes("ctrl"),
+ shiftKey: key.getAttribute("modifiers").includes("shift"),
+ });
+
+ const selectedText = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ return content.promiseTestResult;
+ }
+ );
+ is(
+ selectedText,
+ "Test",
+ "The shortcut key should select all text in the editor"
+ );
+
+ is(
+ key.getAttribute("count"),
+ "0",
+ "The reserved shortcut key should be consumed by the focused editor instead"
+ );
+
+ document.documentElement.removeChild(container);
+
+ BrowserTestUtils.removeTab(tab);
+ }
+ );
+}
diff --git a/browser/base/content/test/permissions/browser_site_scoped_permissions.js b/browser/base/content/test/permissions/browser_site_scoped_permissions.js
new file mode 100644
index 0000000000..560b1fff4c
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_site_scoped_permissions.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const EMPTY_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "empty.html";
+
+const SUBDOMAIN_EMPTY_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://www.example.com"
+ ) + "empty.html";
+
+add_task(async function testSiteScopedPermissionSubdomainAffectsBaseDomain() {
+ let subdomainOrigin = "https://www.example.com";
+ let subdomainPrincipal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ subdomainOrigin
+ );
+ let id = "3rdPartyStorage^https://example.org";
+
+ await BrowserTestUtils.withNewTab(EMPTY_PAGE, async function (browser) {
+ Services.perms.addFromPrincipal(
+ subdomainPrincipal,
+ id,
+ SitePermissions.ALLOW
+ );
+
+ await openPermissionPopup();
+
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let listEntryCount = permissionsList.querySelectorAll(
+ ".permission-popup-permission-item"
+ ).length;
+ is(
+ listEntryCount,
+ 1,
+ "Permission exists on base domain when set on subdomain"
+ );
+
+ closePermissionPopup();
+
+ Services.perms.removeFromPrincipal(subdomainPrincipal, id);
+
+ await openPermissionPopup();
+
+ listEntryCount = permissionsList.querySelectorAll(
+ ".permission-popup-permission-item-3rdPartyStorage"
+ ).length;
+ is(
+ listEntryCount,
+ 0,
+ "Permission removed on base domain when removed on subdomain"
+ );
+
+ await closePermissionPopup();
+ });
+});
+
+add_task(async function testSiteScopedPermissionBaseDomainAffectsSubdomain() {
+ let origin = "https://example.com";
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let id = "3rdPartyStorage^https://example.org";
+
+ await BrowserTestUtils.withNewTab(
+ SUBDOMAIN_EMPTY_PAGE,
+ async function (browser) {
+ Services.perms.addFromPrincipal(principal, id, SitePermissions.ALLOW);
+ await openPermissionPopup();
+
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let listEntryCount = permissionsList.querySelectorAll(
+ ".permission-popup-permission-item"
+ ).length;
+ is(
+ listEntryCount,
+ 1,
+ "Permission exists on base domain when set on subdomain"
+ );
+
+ closePermissionPopup();
+
+ Services.perms.removeFromPrincipal(principal, id);
+
+ await openPermissionPopup();
+
+ listEntryCount = permissionsList.querySelectorAll(
+ ".permission-popup-permission-item-3rdPartyStorage"
+ ).length;
+ is(
+ listEntryCount,
+ 0,
+ "Permission removed on base domain when removed on subdomain"
+ );
+
+ await closePermissionPopup();
+ }
+ );
+});
diff --git a/browser/base/content/test/permissions/browser_temporary_permissions.js b/browser/base/content/test/permissions/browser_temporary_permissions.js
new file mode 100644
index 0000000000..83f7e49d56
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_temporary_permissions.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ORIGIN = "https://example.com";
+const PERMISSIONS_PAGE =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
+ "permissions.html";
+const SUBFRAME_PAGE =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
+ "temporary_permissions_subframe.html";
+
+// Test that setting temp permissions triggers a change in the identity block.
+add_task(async function testTempPermissionChangeEvents() {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(ORIGIN);
+ let id = "geo";
+
+ await BrowserTestUtils.withNewTab(ORIGIN, function (browser) {
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ });
+
+ let geoIcon = document.querySelector(
+ ".blocked-permission-icon[data-permission-id=geo]"
+ );
+
+ Assert.notEqual(
+ geoIcon.getBoundingClientRect().width,
+ 0,
+ "geo anchor should be visible"
+ );
+
+ SitePermissions.removeFromPrincipal(principal, id, browser);
+
+ Assert.equal(
+ geoIcon.getBoundingClientRect().width,
+ 0,
+ "geo anchor should not be visible"
+ );
+ });
+});
+
+// Test that temp blocked permissions requested by subframes (with a different URI) affect the whole page.
+add_task(async function testTempPermissionSubframes() {
+ let uri = NetUtil.newURI(ORIGIN);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ let id = "geo";
+
+ await BrowserTestUtils.withNewTab(SUBFRAME_PAGE, async function (browser) {
+ let popupshown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+
+ // Request a permission.
+ await SpecialPowers.spawn(browser, [uri.host], async function (host0) {
+ let frame = content.document.getElementById("frame");
+
+ await content.SpecialPowers.spawn(frame, [host0], async function (host) {
+ const { E10SUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/E10SUtils.sys.mjs"
+ );
+
+ E10SUtils.wrapHandlingUserInput(this.content, true, function () {
+ let frameDoc = this.content.document;
+
+ // Make sure that the origin of our test page is different.
+ Assert.notEqual(frameDoc.location.host, host);
+
+ frameDoc.getElementById("geo").click();
+ });
+ });
+ });
+
+ await popupshown;
+
+ let popuphidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+
+ let notification = PopupNotifications.panel.firstElementChild;
+ EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+
+ await popuphidden;
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ });
+ });
+});
diff --git a/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js b/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js
new file mode 100644
index 0000000000..e323f769cd
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_temporary_permissions_expiry.js
@@ -0,0 +1,208 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+"use strict";
+
+const ORIGIN = "https://example.com";
+const PERMISSIONS_PAGE =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
+ "permissions.html";
+
+// Ignore promise rejection caused by clicking Deny button.
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+PromiseTestUtils.allowMatchingRejectionsGlobally(/The request is not allowed/);
+
+const EXPIRE_TIME_MS = 100;
+const TIMEOUT_MS = 500;
+
+const EXPIRE_TIME_CUSTOM_MS = 1000;
+const TIMEOUT_CUSTOM_MS = 1500;
+
+const kVREnabled = SpecialPowers.getBoolPref("dom.vr.enabled");
+
+// Test that temporary permissions can be re-requested after they expired
+// and that the identity block is updated accordingly.
+add_task(async function testTempPermissionRequestAfterExpiry() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.temporary_permission_expire_time_ms", EXPIRE_TIME_MS],
+ ["media.navigator.permission.fake", true],
+ ["dom.vr.always_support_vr", true],
+ ],
+ });
+
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(ORIGIN);
+ let ids = ["geo", "camera"];
+
+ if (kVREnabled) {
+ ids.push("xr");
+ }
+
+ for (let id of ids) {
+ await BrowserTestUtils.withNewTab(
+ PERMISSIONS_PAGE,
+ async function (browser) {
+ let blockedIcon = gPermissionPanel._identityPermissionBox.querySelector(
+ `.blocked-permission-icon[data-permission-id='${id}']`
+ );
+
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(principal, id, browser),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ }
+ );
+
+ ok(
+ blockedIcon.hasAttribute("showing"),
+ "blocked permission icon is shown"
+ );
+
+ await new Promise(c => setTimeout(c, TIMEOUT_MS));
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(principal, id, browser),
+ {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ }
+ );
+
+ let popupshown = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popupshown"
+ );
+
+ // Request a permission;
+ await BrowserTestUtils.synthesizeMouseAtCenter(`#${id}`, {}, browser);
+
+ await popupshown;
+
+ ok(
+ !blockedIcon.hasAttribute("showing"),
+ "blocked permission icon is not shown"
+ );
+
+ let popuphidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+
+ let notification = PopupNotifications.panel.firstElementChild;
+ EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+
+ await popuphidden;
+
+ SitePermissions.removeFromPrincipal(principal, id, browser);
+ }
+ );
+ }
+});
+
+/**
+ * Test whether the identity UI shows the permission granted state.
+ * @param {boolean} state - true = Shows permission granted, false otherwise.
+ */
+async function testIdentityPermissionGrantedState(state) {
+ let hasAttribute;
+ let msg = `Identity permission box ${
+ state ? "shows" : "does not show"
+ } granted permissions.`;
+ await TestUtils.waitForCondition(() => {
+ hasAttribute =
+ gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions");
+ return hasAttribute == state;
+ }, msg);
+ is(hasAttribute, state, msg);
+}
+
+// Test that temporary permissions can have custom expiry time and the identity
+// block is updated correctly on expiry.
+add_task(async function testTempPermissionCustomExpiry() {
+ const TEST_ID = "geo";
+ // Set a default expiry time which is lower than the custom one we'll set.
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.temporary_permission_expire_time_ms", EXPIRE_TIME_MS]],
+ });
+
+ await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async browser => {
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(null, TEST_ID, browser),
+ {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ "Permission not set initially"
+ );
+
+ await testIdentityPermissionGrantedState(false);
+
+ // Set permission with custom expiry time.
+ SitePermissions.setForPrincipal(
+ null,
+ "geo",
+ SitePermissions.ALLOW,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser,
+ EXPIRE_TIME_CUSTOM_MS
+ );
+
+ await testIdentityPermissionGrantedState(true);
+
+ // We've set the permission, start the timer promise.
+ let timeout = new Promise(resolve =>
+ setTimeout(resolve, TIMEOUT_CUSTOM_MS)
+ );
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(null, TEST_ID, browser),
+ {
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ },
+ "We should see the temporary permission we just set."
+ );
+
+ // Wait for half of the expiry time.
+ await new Promise(resolve =>
+ setTimeout(resolve, EXPIRE_TIME_CUSTOM_MS / 2)
+ );
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(null, TEST_ID, browser),
+ {
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ },
+ "Temporary permission should not have expired yet."
+ );
+
+ // Wait until permission expiry.
+ await timeout;
+
+ // Identity permission section should have updated by now. It should do this
+ // without relying on side-effects of the SitePermissions getter.
+ await testIdentityPermissionGrantedState(false);
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(null, TEST_ID, browser),
+ {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ "Permission should have expired"
+ );
+ });
+});
diff --git a/browser/base/content/test/permissions/browser_temporary_permissions_navigation.js b/browser/base/content/test/permissions/browser_temporary_permissions_navigation.js
new file mode 100644
index 0000000000..7da79b1810
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_temporary_permissions_navigation.js
@@ -0,0 +1,239 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that temporary permissions are removed on user initiated reload only.
+add_task(async function testTempPermissionOnReload() {
+ let origin = "https://example.com/";
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let id = "geo";
+
+ await BrowserTestUtils.withNewTab(origin, async function (browser) {
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ let reloaded = BrowserTestUtils.browserLoaded(browser, false, origin);
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ });
+
+ // Reload through the page (should not remove the temp permission).
+ await SpecialPowers.spawn(browser, [], () =>
+ content.document.location.reload()
+ );
+
+ await reloaded;
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ });
+
+ reloaded = BrowserTestUtils.browserLoaded(browser, false, origin);
+
+ // Reload as a user (should remove the temp permission).
+ BrowserReload();
+
+ await reloaded;
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // Set the permission again.
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ // Open the tab context menu.
+ let contextMenu = document.getElementById("tabContextMenu");
+ // The TabContextMenu initializes its strings only on a focus or mouseover event.
+ // Calls focus event on the TabContextMenu early in the test.
+ gBrowser.selectedTab.focus();
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(gBrowser.selectedTab, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await popupShownPromise;
+
+ let reloadMenuItem = document.getElementById("context_reloadTab");
+
+ reloaded = BrowserTestUtils.browserLoaded(browser, false, origin);
+
+ // Reload as a user through the context menu (should remove the temp permission).
+ contextMenu.activateItem(reloadMenuItem);
+
+ await reloaded;
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // Set the permission again.
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ // Reload as user via return key in urlbar (should remove the temp permission)
+ let urlBarInput = document.getElementById("urlbar-input");
+ await EventUtils.synthesizeMouseAtCenter(urlBarInput, {});
+
+ reloaded = BrowserTestUtils.browserLoaded(browser, false, origin);
+
+ EventUtils.synthesizeAndWaitKey("VK_RETURN", {});
+
+ await reloaded;
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ SitePermissions.removeFromPrincipal(principal, id, browser);
+ });
+});
+
+// Test that temporary permissions are not removed when reloading all tabs.
+add_task(async function testTempPermissionOnReloadAllTabs() {
+ let origin = "https://example.com/";
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let id = "geo";
+
+ await BrowserTestUtils.withNewTab(origin, async function (browser) {
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ // Select all tabs before opening the context menu.
+ gBrowser.selectAllTabs();
+
+ // Open the tab context menu.
+ let contextMenu = document.getElementById("tabContextMenu");
+ // The TabContextMenu initializes its strings only on a focus or mouseover event.
+ // Calls focus event on the TabContextMenu early in the test.
+ gBrowser.selectedTab.focus();
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(gBrowser.selectedTab, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await popupShownPromise;
+
+ let reloadMenuItem = document.getElementById("context_reloadSelectedTabs");
+
+ let reloaded = Promise.all(
+ gBrowser.visibleTabs.map(tab =>
+ BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab))
+ )
+ );
+ contextMenu.activateItem(reloadMenuItem);
+ await reloaded;
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ });
+
+ SitePermissions.removeFromPrincipal(principal, id, browser);
+ });
+});
+
+// Test that temporary permissions are persisted through navigation in a tab.
+add_task(async function testTempPermissionOnNavigation() {
+ let origin = "https://example.com/";
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let id = "geo";
+
+ await BrowserTestUtils.withNewTab(origin, async function (browser) {
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ browser
+ );
+
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, id, browser), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ });
+
+ let loaded = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ "https://example.org/"
+ );
+
+ // Navigate to another domain.
+ await SpecialPowers.spawn(
+ browser,
+ [],
+ () => (content.document.location = "https://example.org/")
+ );
+
+ await loaded;
+
+ // The temporary permissions for the current URI should be reset.
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(browser.contentPrincipal, id, browser),
+ {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ }
+ );
+
+ loaded = BrowserTestUtils.browserLoaded(browser, false, origin);
+
+ // Navigate to the original domain.
+ await SpecialPowers.spawn(
+ browser,
+ [],
+ () => (content.document.location = "https://example.com/")
+ );
+
+ await loaded;
+
+ // The temporary permissions for the original URI should still exist.
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(browser.contentPrincipal, id, browser),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ }
+ );
+
+ SitePermissions.removeFromPrincipal(browser.contentPrincipal, id, browser);
+ });
+});
diff --git a/browser/base/content/test/permissions/browser_temporary_permissions_tabs.js b/browser/base/content/test/permissions/browser_temporary_permissions_tabs.js
new file mode 100644
index 0000000000..a4347f9671
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_temporary_permissions_tabs.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that temp permissions are persisted through moving tabs to new windows.
+add_task(async function testTempPermissionOnTabMove() {
+ let origin = "https://example.com/";
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let id = "geo";
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, origin);
+
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ tab.linkedBrowser
+ );
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(principal, id, tab.linkedBrowser),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ }
+ );
+
+ let promiseWin = BrowserTestUtils.waitForNewWindow();
+ gBrowser.replaceTabWithWindow(tab);
+ let win = await promiseWin;
+ tab = win.gBrowser.selectedTab;
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(principal, id, tab.linkedBrowser),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ }
+ );
+
+ SitePermissions.removeFromPrincipal(principal, id, tab.linkedBrowser);
+ await BrowserTestUtils.closeWindow(win);
+});
+
+// Test that temp permissions don't affect other tabs of the same URI.
+add_task(async function testTempPermissionMultipleTabs() {
+ let origin = "https://example.com/";
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let id = "geo";
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, origin);
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, origin);
+
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ tab2.linkedBrowser
+ );
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(principal, id, tab2.linkedBrowser),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ }
+ );
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(principal, id, tab1.linkedBrowser),
+ {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ }
+ );
+
+ let geoIcon = document.querySelector(
+ ".blocked-permission-icon[data-permission-id=geo]"
+ );
+
+ Assert.notEqual(
+ geoIcon.getBoundingClientRect().width,
+ 0,
+ "geo anchor should be visible"
+ );
+
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+
+ Assert.equal(
+ geoIcon.getBoundingClientRect().width,
+ 0,
+ "geo anchor should not be visible"
+ );
+
+ SitePermissions.removeFromPrincipal(principal, id, tab2.linkedBrowser);
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+// Test that temp permissions are cleared when closing tabs.
+add_task(async function testTempPermissionOnTabClose() {
+ let origin = "https://example.com/";
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
+ let id = "geo";
+
+ ok(
+ !SitePermissions._temporaryPermissions._stateByBrowser.size,
+ "Temporary permission map should be empty initially."
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, origin);
+
+ SitePermissions.setForPrincipal(
+ principal,
+ id,
+ SitePermissions.BLOCK,
+ SitePermissions.SCOPE_TEMPORARY,
+ tab.linkedBrowser
+ );
+
+ Assert.deepEqual(
+ SitePermissions.getForPrincipal(principal, id, tab.linkedBrowser),
+ {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_TEMPORARY,
+ }
+ );
+
+ ok(
+ SitePermissions._temporaryPermissions._stateByBrowser.has(
+ tab.linkedBrowser
+ ),
+ "Temporary permission map should have an entry for the browser."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ ok(
+ !SitePermissions._temporaryPermissions._stateByBrowser.size,
+ "Temporary permission map should be empty after closing the tab."
+ );
+});
diff --git a/browser/base/content/test/permissions/dummy.js b/browser/base/content/test/permissions/dummy.js
new file mode 100644
index 0000000000..c45ec0a714
--- /dev/null
+++ b/browser/base/content/test/permissions/dummy.js
@@ -0,0 +1 @@
+// Just a dummy file for testing.
diff --git a/browser/base/content/test/permissions/empty.html b/browser/base/content/test/permissions/empty.html
new file mode 100644
index 0000000000..1ad28bb1f7
--- /dev/null
+++ b/browser/base/content/test/permissions/empty.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Empty file</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/base/content/test/permissions/head.js b/browser/base/content/test/permissions/head.js
new file mode 100644
index 0000000000..847386b7e2
--- /dev/null
+++ b/browser/base/content/test/permissions/head.js
@@ -0,0 +1,28 @@
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+SpecialPowers.addTaskImport(
+ "E10SUtils",
+ "resource://gre/modules/E10SUtils.sys.mjs"
+);
+
+function openPermissionPopup() {
+ let promise = BrowserTestUtils.waitForEvent(
+ gBrowser.ownerGlobal,
+ "popupshown",
+ true,
+ event => event.target == gPermissionPanel._permissionPopup
+ );
+ gPermissionPanel._identityPermissionBox.click();
+ return promise;
+}
+
+function closePermissionPopup() {
+ let promise = BrowserTestUtils.waitForEvent(
+ gPermissionPanel._permissionPopup,
+ "popuphidden"
+ );
+ gPermissionPanel._permissionPopup.hidePopup();
+ return promise;
+}
diff --git a/browser/base/content/test/permissions/permissions.html b/browser/base/content/test/permissions/permissions.html
new file mode 100644
index 0000000000..97286914e7
--- /dev/null
+++ b/browser/base/content/test/permissions/permissions.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+<script>
+var gKeyDowns = 0;
+var gKeyPresses = 0;
+
+navigator.serviceWorker.register("dummy.js");
+
+function requestPush() {
+ return navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
+ serviceWorkerRegistration.pushManager.subscribe();
+ });
+}
+
+function requestGeo() {
+ return navigator.geolocation.getCurrentPosition(() => {
+ parent.postMessage("allow", "*");
+ }, error => {
+ // PERMISSION_DENIED = 1
+ parent.postMessage(error.code == 1 ? "deny" : "allow", "*");
+ });
+}
+
+
+window.onmessage = function(event) {
+ switch (event.data) {
+ case "push":
+ requestPush();
+ break;
+ }
+};
+
+</script>
+ <body onkeydown="gKeyDowns++;" onkeypress="gKeyPresses++">
+ <!-- This page could eventually request permissions from content
+ and make sure that chrome responds appropriately -->
+ <button id="geo" onclick="requestGeo()">Geolocation</button>
+ <button id="xr" onclick="navigator.getVRDisplays()">XR</button>
+ <button id="desktop-notification" onclick="Notification.requestPermission()">Notifications</button>
+ <button id="push" onclick="requestPush()">Push Notifications</button>
+ <button id="camera" onclick="navigator.mediaDevices.getUserMedia({video: true, fake: true})">Camera</button>
+ </body>
+</html>
diff --git a/browser/base/content/test/permissions/temporary_permissions_frame.html b/browser/base/content/test/permissions/temporary_permissions_frame.html
new file mode 100644
index 0000000000..25aede980f
--- /dev/null
+++ b/browser/base/content/test/permissions/temporary_permissions_frame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Permissions Subframe Test</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+ <iframe id="frameAncestor"
+ src="https://test1.example.com/browser/browser/base/content/test/permissions/temporary_permissions_subframe.html"
+ allow="geolocation https://test1.example.com https://test2.example.com"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/permissions/temporary_permissions_subframe.html b/browser/base/content/test/permissions/temporary_permissions_subframe.html
new file mode 100644
index 0000000000..4ff13f2e91
--- /dev/null
+++ b/browser/base/content/test/permissions/temporary_permissions_subframe.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Temporary Permissions Subframe Test</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+ <iframe id="frame" src="https://example.org/browser/browser/base/content/test/permissions/permissions.html" allow="geolocation"></iframe>
+ <iframe id="frameAllowsAll" src="https://example.org/browser/browser/base/content/test/permissions/permissions.html" allow="geolocation *"></iframe>
+</body>
+</html>