mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Bug 1522120 - Remove permission prompts when entering full-screen and leave full-screen when a permission prompt is shown. r=johannh
Differential Revision: https://phabricator.services.mozilla.com/D36932 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
a908700415
commit
39687fe7bb
@ -403,6 +403,8 @@ pref("permissions.desktop-notification.postPrompt.enabled", true);
|
||||
pref("permissions.desktop-notification.postPrompt.enabled", false);
|
||||
#endif
|
||||
|
||||
pref("permissions.fullscreen.allowed", false);
|
||||
|
||||
pref("permissions.postPrompt.animate", true);
|
||||
|
||||
// This is primarily meant to be enabled for studies.
|
||||
|
@ -443,7 +443,7 @@ var gXPInstallObserver = {
|
||||
PopupNotifications.getNotification(id, browser)
|
||||
).filter(notification => notification != null);
|
||||
|
||||
PopupNotifications.remove(notifications);
|
||||
PopupNotifications.remove(notifications, true);
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic, aData) {
|
||||
|
@ -6,6 +6,8 @@
|
||||
// This file is loaded into the browser window scope.
|
||||
/* eslint-env mozilla/browser-window */
|
||||
|
||||
ChromeUtils.import("resource:///modules/PermissionUI.jsm", this);
|
||||
|
||||
var PointerlockFsWarning = {
|
||||
_element: null,
|
||||
_origin: null,
|
||||
@ -246,7 +248,19 @@ var FullScreen = {
|
||||
"DOMFullscreen:Painted",
|
||||
],
|
||||
|
||||
_permissionNotificationIDs: Object.values(PermissionUI)
|
||||
.filter(value => value.prototype && value.prototype.notificationID)
|
||||
.map(value => value.prototype.notificationID)
|
||||
// Additionally include webRTC permission prompt which does not use PermissionUI
|
||||
.concat(["webRTC-shareDevices"]),
|
||||
|
||||
init() {
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"permissionsFullScreenAllowed",
|
||||
"permissions.fullscreen.allowed"
|
||||
);
|
||||
|
||||
// called when we go into full screen, even if initiated by a web page script
|
||||
window.addEventListener("fullscreen", this, true);
|
||||
window.addEventListener("willenterfullscreen", this, true);
|
||||
@ -385,11 +399,6 @@ var FullScreen = {
|
||||
browser = event.target.ownerGlobal.docShell.chromeEventHandler;
|
||||
}
|
||||
|
||||
// Addon installation should be cancelled when entering fullscreen for security and usability reasons.
|
||||
// Installation prompts in fullscreen can trick the user into installing unwanted addons.
|
||||
// In fullscreen the notification box does not have a clear visual association with its parent anymore.
|
||||
gXPInstallObserver.removeAllNotifications(browser);
|
||||
|
||||
TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
|
||||
this.enterDomFullscreen(browser);
|
||||
break;
|
||||
@ -401,6 +410,18 @@ var FullScreen = {
|
||||
}
|
||||
},
|
||||
|
||||
_handlePermPromptShow() {
|
||||
if (
|
||||
!FullScreen.permissionsFullScreenAllowed &&
|
||||
window.fullScreen &&
|
||||
PopupNotifications.getNotification(
|
||||
this._permissionNotificationIDs
|
||||
).filter(n => !n.dismissed).length > 0
|
||||
) {
|
||||
this.exitDomFullScreen();
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage(aMessage) {
|
||||
let browser = aMessage.target;
|
||||
switch (aMessage.name) {
|
||||
@ -465,6 +486,14 @@ var FullScreen = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove permission prompts when entering full-screen.
|
||||
if (!FullScreen.permissionsFullScreenAllowed) {
|
||||
let notifications = PopupNotifications.getNotification(
|
||||
this._permissionNotificationIDs
|
||||
).filter(n => !n.dismissed);
|
||||
PopupNotifications.remove(notifications, true);
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute("inDOMFullscreen", true);
|
||||
|
||||
if (gFindBarInitialized) {
|
||||
@ -478,6 +507,17 @@ var FullScreen = {
|
||||
// If a fullscreen window loses focus, we show a warning when the
|
||||
// fullscreen window is refocused.
|
||||
window.addEventListener("activate", this);
|
||||
|
||||
// Addon installation should be cancelled when entering fullscreen for security and usability reasons.
|
||||
// Installation prompts in fullscreen can trick the user into installing unwanted addons.
|
||||
// In fullscreen the notification box does not have a clear visual association with its parent anymore.
|
||||
gXPInstallObserver.removeAllNotifications(aBrowser);
|
||||
|
||||
PopupNotifications.panel.addEventListener(
|
||||
"popupshowing",
|
||||
() => this._handlePermPromptShow(),
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
cleanup() {
|
||||
@ -490,6 +530,11 @@ var FullScreen = {
|
||||
},
|
||||
|
||||
cleanupDomFullscreen() {
|
||||
PopupNotifications.panel.removeEventListener(
|
||||
"popupshowing",
|
||||
() => this._handlePermPromptShow(),
|
||||
true
|
||||
);
|
||||
window.messageManager.broadcastAsyncMessage("DOMFullscreen:CleanUp");
|
||||
|
||||
PointerlockFsWarning.close();
|
||||
|
@ -3,3 +3,5 @@ support-files =
|
||||
head.js
|
||||
[browser_bug1557041.js]
|
||||
skip-if = os == 'linux' # Bug 1561973
|
||||
[browser_fullscreen_permissions_prompt.js]
|
||||
skip-if = debug && os == 'macos' # Bug 1568570
|
@ -0,0 +1,156 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// This test tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
|
||||
SimpleTest.requestCompleteLog();
|
||||
|
||||
async function requestNotificationPermission(browser) {
|
||||
return ContentTask.spawn(browser, null, () => {
|
||||
return content.Notification.requestPermission();
|
||||
});
|
||||
}
|
||||
|
||||
async function requestCameraPermission(browser) {
|
||||
return ContentTask.spawn(browser, null, () => {
|
||||
return new Promise(resolve => {
|
||||
content.navigator.mediaDevices
|
||||
.getUserMedia({ video: true, fake: true })
|
||||
.catch(resolve(false))
|
||||
.then(resolve(true));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_fullscreen_closes_permissionui_prompt() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.webnotifications.requireuserinteraction", false],
|
||||
["permissions.fullscreen.allowed", false],
|
||||
],
|
||||
});
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"https://example.com"
|
||||
);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
let popupShown, requestResult, popupHidden;
|
||||
|
||||
popupShown = BrowserTestUtils.waitForEvent(
|
||||
window.PopupNotifications.panel,
|
||||
"popupshown"
|
||||
);
|
||||
|
||||
info("Requesting notification permission");
|
||||
requestResult = requestNotificationPermission(browser);
|
||||
await popupShown;
|
||||
|
||||
info("Entering DOM full-screen");
|
||||
popupHidden = BrowserTestUtils.waitForEvent(
|
||||
window.PopupNotifications.panel,
|
||||
"popuphidden"
|
||||
);
|
||||
|
||||
await changeFullscreen(browser, true);
|
||||
|
||||
await popupHidden;
|
||||
|
||||
is(
|
||||
await requestResult,
|
||||
"default",
|
||||
"Expect permission request to be cancelled"
|
||||
);
|
||||
|
||||
await changeFullscreen(browser, false);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(async function test_fullscreen_closes_webrtc_permission_prompt() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["media.navigator.permission.fake", true],
|
||||
["media.navigator.permission.force", true],
|
||||
["permissions.fullscreen.allowed", false],
|
||||
],
|
||||
});
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"https://example.com"
|
||||
);
|
||||
let browser = tab.linkedBrowser;
|
||||
let popupShown, requestResult, popupHidden;
|
||||
|
||||
popupShown = BrowserTestUtils.waitForEvent(
|
||||
window.PopupNotifications.panel,
|
||||
"popupshown"
|
||||
);
|
||||
|
||||
info("Requesting camera permission");
|
||||
requestResult = requestCameraPermission(browser);
|
||||
|
||||
await popupShown;
|
||||
|
||||
info("Entering DOM full-screen");
|
||||
popupHidden = BrowserTestUtils.waitForEvent(
|
||||
window.PopupNotifications.panel,
|
||||
"popuphidden"
|
||||
);
|
||||
await changeFullscreen(browser, true);
|
||||
|
||||
await popupHidden;
|
||||
|
||||
is(
|
||||
await requestResult,
|
||||
false,
|
||||
"Expect webrtc permission request to be cancelled"
|
||||
);
|
||||
|
||||
await changeFullscreen(browser, false);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(async function test_permission_prompt_closes_fullscreen() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.webnotifications.requireuserinteraction", false],
|
||||
["permissions.fullscreen.allowed", false],
|
||||
],
|
||||
});
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"https://example.com"
|
||||
);
|
||||
let browser = tab.linkedBrowser;
|
||||
info("Entering DOM full-screen");
|
||||
await changeFullscreen(browser, true);
|
||||
|
||||
let popupShown = BrowserTestUtils.waitForEvent(
|
||||
window.PopupNotifications.panel,
|
||||
"popupshown"
|
||||
);
|
||||
let fullScreenExit = waitForFullScreenState(browser, false);
|
||||
|
||||
info("Requesting notification permission");
|
||||
requestNotificationPermission(browser);
|
||||
await popupShown;
|
||||
|
||||
info("Waiting for full-screen exit");
|
||||
await fullScreenExit;
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
@ -6,10 +6,29 @@ const { ContentTask } = ChromeUtils.import(
|
||||
);
|
||||
|
||||
function waitForFullScreenState(browser, state) {
|
||||
let eventName = state
|
||||
? "MozDOMFullscreen:Entered"
|
||||
: "MozDOMFullscreen:Exited";
|
||||
return BrowserTestUtils.waitForEvent(browser.ownerGlobal, eventName);
|
||||
return new Promise(resolve => {
|
||||
let eventReceived = false;
|
||||
window.messageManager.addMessageListener(
|
||||
"DOMFullscreen:Painted",
|
||||
function listener() {
|
||||
if (!eventReceived) {
|
||||
return;
|
||||
}
|
||||
window.messageManager.removeMessageListener(
|
||||
"DOMFullscreen:Painted",
|
||||
listener
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
window.addEventListener(
|
||||
`MozDOMFullscreen:${state ? "Entered" : "Exited"}`,
|
||||
() => {
|
||||
eventReceived = true;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,9 +37,17 @@ function waitForFullScreenState(browser, state) {
|
||||
* @param {Boolean} fullscreenState - true to enter fullscreen, false to leave
|
||||
* @returns {Promise} - Resolves once fullscreen change is applied
|
||||
*/
|
||||
function changeFullscreen(browser, fullScreenState) {
|
||||
async function changeFullscreen(browser, fullScreenState) {
|
||||
await new Promise(resolve =>
|
||||
SimpleTest.waitForFocus(resolve, browser.ownerGlobal)
|
||||
);
|
||||
let fullScreenChange = waitForFullScreenState(browser, fullScreenState);
|
||||
ContentTask.spawn(browser, fullScreenState, state => {
|
||||
ContentTask.spawn(browser, fullScreenState, async state => {
|
||||
// Wait for document focus before requesting full-screen
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => docShell.isActive && content.document.hasFocus(),
|
||||
"Waiting for document focus"
|
||||
);
|
||||
if (state) {
|
||||
content.document.body.requestFullscreen();
|
||||
} else {
|
||||
|
@ -625,7 +625,7 @@ var PermissionPromptPrototype = {
|
||||
options.hideClose = true;
|
||||
}
|
||||
|
||||
options.eventCallback = (topic, nextRemovalReason) => {
|
||||
options.eventCallback = (topic, nextRemovalReason, isCancel) => {
|
||||
// When the docshell of the browser is aboout to be swapped to another one,
|
||||
// the "swapping" event is called. Returning true causes the notification
|
||||
// to be moved to the new browser.
|
||||
@ -653,6 +653,9 @@ var PermissionPromptPrototype = {
|
||||
nextRemovalReason
|
||||
);
|
||||
}
|
||||
if (isCancel) {
|
||||
this.cancel();
|
||||
}
|
||||
this.onAfterShow();
|
||||
}
|
||||
return false;
|
||||
|
@ -572,7 +572,7 @@ function prompt(aBrowser, aRequest) {
|
||||
name: getHostOrExtensionName(principal.URI),
|
||||
persistent: true,
|
||||
hideClose: true,
|
||||
eventCallback(aTopic, aNewBrowser) {
|
||||
eventCallback(aTopic, aNewBrowser, isCancel) {
|
||||
if (aTopic == "swapping") {
|
||||
return true;
|
||||
}
|
||||
@ -602,6 +602,11 @@ function prompt(aBrowser, aRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// If the notification has been cancelled (e.g. due to entering full-screen), also cancel the webRTC request
|
||||
if (aTopic == "removed" && notification && isCancel) {
|
||||
denyRequest(notification.browser, aRequest);
|
||||
}
|
||||
|
||||
if (aTopic != "showing") {
|
||||
return false;
|
||||
}
|
||||
|
@ -346,20 +346,24 @@ PopupNotifications.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve a Notification object associated with the browser/ID pair.
|
||||
* @param id
|
||||
* The Notification ID to search for.
|
||||
* @param browser
|
||||
* Retrieve one or many Notification object/s associated with the browser/ID pair.
|
||||
* @param {string|string[]} id
|
||||
* The Notification ID or an array of IDs to search for.
|
||||
* @param [browser]
|
||||
* The browser whose notifications should be searched. If null, the
|
||||
* currently selected browser's notifications will be searched.
|
||||
*
|
||||
* @returns the corresponding Notification object, or null if no such
|
||||
* @returns {Notification|Notification[]|null} If passed a single id, returns the corresponding Notification object, or null if no such
|
||||
* notification exists.
|
||||
* If passed an id array, returns an array of Notification objects which match the ids.
|
||||
*/
|
||||
getNotification: function PopupNotifications_getNotification(id, browser) {
|
||||
let notifications = this._getNotificationsForBrowser(
|
||||
browser || this.tabbrowser.selectedBrowser
|
||||
);
|
||||
if (Array.isArray(id)) {
|
||||
return notifications.filter(x => id.includes(x.id));
|
||||
}
|
||||
return notifications.find(x => x.id == id) || null;
|
||||
},
|
||||
|
||||
@ -732,15 +736,18 @@ PopupNotifications.prototype = {
|
||||
/**
|
||||
* Removes one or many Notifications.
|
||||
* @param {Notification|Notification[]} notification - The Notification object/s to remove.
|
||||
* @param {Boolean} [isCancel] - Whether to signal, in the notification event, that removal
|
||||
* should be treated as cancel. This is currently used to cancel permission requests
|
||||
* when their Notifications are removed.
|
||||
*/
|
||||
remove: function PopupNotifications_remove(notification) {
|
||||
remove: function PopupNotifications_remove(notification, isCancel = false) {
|
||||
let notificationArray = Array.isArray(notification)
|
||||
? notification
|
||||
: [notification];
|
||||
let activeBrowser;
|
||||
|
||||
notificationArray.forEach(n => {
|
||||
this._remove(n);
|
||||
this._remove(n, isCancel);
|
||||
if (!activeBrowser && this._isActiveBrowser(n.browser)) {
|
||||
activeBrowser = n.browser;
|
||||
}
|
||||
@ -796,7 +803,10 @@ PopupNotifications.prototype = {
|
||||
: [];
|
||||
},
|
||||
|
||||
_remove: function PopupNotifications_removeHelper(notification) {
|
||||
_remove: function PopupNotifications_removeHelper(
|
||||
notification,
|
||||
isCancel = false
|
||||
) {
|
||||
// This notification may already be removed, in which case let's just fail
|
||||
// silently.
|
||||
let notifications = this._getNotificationsForBrowser(notification.browser);
|
||||
@ -818,7 +828,8 @@ PopupNotifications.prototype = {
|
||||
this._fireCallback(
|
||||
notification,
|
||||
NOTIFICATION_EVENT_REMOVED,
|
||||
this.nextRemovalReason
|
||||
this.nextRemovalReason,
|
||||
isCancel
|
||||
);
|
||||
},
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user