Bug 1580567 - Implement XR device access permission UI r=fluent-reviewers,bzbarsky,pbz,daoshengmu,imanol

Added @rbarker as a reviewer to check if this will work well within GeckoView for FxR / Android.
Added @bzbarsky for test_interfaces.html.  -- I'd like to re-land the secure origin requirement for WebVR as part of this patch, as it doesn't help to have UI that can't guarantee the identity of the origin.  (This was backed out due to test failures originally, and since been fixed)

Differential Revision: https://phabricator.services.mozilla.com/D45951

--HG--
rename : browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js => browser/components/privatebrowsing/test/browser/browser_privatebrowsing_rememberprompt.js
extra : moz-landing-system : lando
This commit is contained in:
Kearwood Kip Gilbert 2019-12-12 03:10:51 +00:00
Родитель add260fb98
Коммит 19ba3b0322
42 изменённых файлов: 603 добавлений и 81 удалений

Просмотреть файл

@ -402,6 +402,7 @@ pref("permissions.manager.defaultsUrl", "resource://app/defaults/permissions");
pref("permissions.default.camera", 0);
pref("permissions.default.microphone", 0);
pref("permissions.default.geo", 0);
pref("permissions.default.xr", 0);
pref("permissions.default.desktop-notification", 0);
pref("permissions.default.shortcuts", 0);
@ -816,6 +817,7 @@ pref("gecko.handlerService.schemes.ircs.3.name", "chrome://browser-region/locale
pref("gecko.handlerService.schemes.ircs.3.uriTemplate", "chrome://browser-region/locale/region.properties");
pref("browser.geolocation.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/geolocation/");
pref("browser.xr.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/xr/");
pref("browser.sessionstore.resume_from_crash", true);
pref("browser.sessionstore.resume_session_once", false);

Просмотреть файл

@ -290,6 +290,11 @@ var gIdentityHandler = {
return (this._geoSharingIcon = document.getElementById("geo-sharing-icon"));
},
get _xrSharingIcon() {
delete this._xrSharingIcon;
return (this._xrSharingIcon = document.getElementById("xr-sharing-icon"));
},
get _webRTCSharingIcon() {
delete this._webRTCSharingIcon;
return (this._webRTCSharingIcon = document.getElementById(
@ -572,6 +577,7 @@ var gIdentityHandler = {
this._webRTCSharingIcon.removeAttribute("paused");
this._webRTCSharingIcon.removeAttribute("sharing");
this._geoSharingIcon.removeAttribute("sharing");
this._xrSharingIcon.removeAttribute("sharing");
if (this._sharingState) {
if (
@ -591,6 +597,9 @@ var gIdentityHandler = {
if (this._sharingState.geo) {
this._geoSharingIcon.setAttribute("sharing", this._sharingState.geo);
}
if (this._sharingState.xr) {
this._xrSharingIcon.setAttribute("sharing", this._sharingState.xr);
}
}
if (this._identityPopup.state == "open") {
@ -1345,6 +1354,20 @@ var gIdentityHandler = {
}
}
if (this._sharingState && this._sharingState.xr) {
let xrPermission = permissions.find(perm => perm.id === "xr");
if (xrPermission) {
xrPermission.sharingState = true;
} else {
permissions.push({
id: "xr",
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_REQUEST,
sharingState: true,
});
}
}
if (this._sharingState && this._sharingState.webRTC) {
let webrtcState = this._sharingState.webRTC;
// If WebRTC device or screen permissions are in use, we need to find
@ -1572,9 +1595,12 @@ var gIdentityHandler = {
return container;
}
if (aPermission.id == "geo") {
if (aPermission.id == "geo" || aPermission.id == "xr") {
let block = document.createXULElement("vbox");
block.setAttribute("id", "identity-popup-geo-container");
block.setAttribute(
"id",
"identity-popup-" + aPermission.id + "-container"
);
let button = this._createPermissionClearButton(aPermission, block);
container.appendChild(button);
@ -1608,8 +1634,8 @@ var gIdentityHandler = {
let browser = gBrowser.selectedBrowser;
this._permissionList.removeChild(container);
if (aPermission.sharingState) {
if (aPermission.id === "geo") {
let origins = browser.getDevicePermissionOrigins("geo");
if (aPermission.id === "geo" || aPermission.id === "xr") {
let origins = browser.getDevicePermissionOrigins(aPermission.id);
for (let origin of origins) {
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
origin
@ -1664,6 +1690,8 @@ var gIdentityHandler = {
if (aPermission.id === "geo") {
gBrowser.updateBrowserSharing(browser, { geo: false });
} else if (aPermission.id === "xr") {
gBrowser.updateBrowserSharing(browser, { xr: false });
}
});

Просмотреть файл

@ -888,10 +888,13 @@
<box style="pointer-events: none;">
<image class="sharing-icon" id="webrtc-sharing-icon"/>
<image class="sharing-icon geo-icon" id="geo-sharing-icon"/>
<image class="sharing-icon xr-icon" id="xr-sharing-icon"/>
</box>
<box id="blocked-permissions-container" align="center">
<image data-permission-id="geo" class="blocked-permission-icon geo-icon" role="button"
data-l10n-id="urlbar-geolocation-blocked"/>
<image data-permission-id="xr" class="blocked-permission-icon xr-icon" role="button"
data-l10n-id="urlbar-xr-blocked"/>
<image data-permission-id="desktop-notification" class="blocked-permission-icon desktop-notification-icon" role="button"
data-l10n-id="urlbar-web-notifications-blocked"/>
<image data-permission-id="camera" class="blocked-permission-icon camera-icon" role="button"
@ -922,6 +925,8 @@
data-l10n-id="urlbar-default-notification-anchor"/>
<image id="geo-notification-icon" class="notification-anchor-icon geo-icon" role="button"
data-l10n-id="urlbar-geolocation-notification-anchor"/>
<image id="xr-notification-icon" class="notification-anchor-icon xr-icon" role="button"
data-l10n-id="urlbar-xr-notification-anchor"/>
<image id="autoplay-media-notification-icon" class="notification-anchor-icon autoplay-media-icon" role="button"
data-l10n-id="urlbar-autoplay-notification-anchor"/>
<image id="addons-notification-icon" class="notification-anchor-icon install-icon" role="button"

Просмотреть файл

@ -31,7 +31,7 @@ add_task(async function testTempPermissionRequestAfterExpiry() {
let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
ORIGIN
);
let ids = ["geo", "camera"];
let ids = ["geo", "camera", "xr"];
for (let id of ids) {
await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function(

Просмотреть файл

@ -41,6 +41,7 @@ window.onmessage = function(event) {
<!-- 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>

Просмотреть файл

@ -30,7 +30,10 @@ async function check(contentTask, options = {}) {
let panel = await popupShownPromise;
let notification = panel.children[0];
let body = notification.querySelector(".popup-notification-body");
if (notification.id == "geolocation-notification") {
if (
notification.id == "geolocation-notification" ||
notification.id == "xr-notification"
) {
ok(
body.innerHTML.includes("local file"),
`file:// URIs should be displayed as local file.`
@ -96,6 +99,12 @@ add_task(async function test_displayURI_geo() {
});
});
add_task(async function test_displayURI_xr() {
await check(async function() {
content.navigator.getVRDisplays();
});
});
add_task(async function test_displayURI_camera() {
await check(async function() {
content.navigator.mediaDevices.getUserMedia({ video: true, fake: true });
@ -115,6 +124,18 @@ add_task(async function test_displayURI_geo_blob() {
);
});
add_task(async function test_displayURI_xr_blob() {
await check(
async function() {
let text = "<script>navigator.getVRDisplays()</script>";
let blob = new Blob([text], { type: "text/html" });
let url = content.URL.createObjectURL(blob);
content.location.href = url;
},
{ skipOnExtension: true }
);
});
add_task(async function test_displayURI_camera_blob() {
await check(
async function() {

Просмотреть файл

@ -4001,6 +4001,9 @@ const ContentPermissionIntegration = {
case "geolocation": {
return new PermissionUI.GeolocationPermissionPrompt(request);
}
case "xr": {
return new PermissionUI.XRPermissionPrompt(request);
}
case "desktop-notification": {
return new PermissionUI.DesktopNotificationPermissionPrompt(request);
}

Просмотреть файл

@ -776,6 +776,30 @@
" />
</hbox>
</hbox>
<hbox id="xrSettingsRow" align="center" role="group" aria-labelledby="xrPermissionsLabel">
<description flex="1">
<image class="xr-icon permission-icon" />
<separator orient="vertical" class="thin"/>
<label id="xrPermissionsLabel" data-l10n-id="permissions-xr"/>
</description>
<hbox pack="end">
<button id="xrSettingsButton"
is="highlightable-button"
class="accessory-button"
data-l10n-id="permissions-xr-settings"
search-l10n-ids="
permissions-remove.label,
permissions-remove-all.label,
permissions-button-cancel.label,
permissions-button-ok.label,
permissions-site-xr-window.title,
permissions-site-xr-desc,
permissions-site-xr-disable-label,
permissions-site-xr-disable-desc,
" />
</hbox>
</hbox>
</vbox>
<separator flex="1"/>

Просмотреть файл

@ -537,6 +537,11 @@ var gPrivacyPane = {
"command",
gPrivacyPane.showLocationExceptions
);
setEventListener(
"xrSettingsButton",
"command",
gPrivacyPane.showXRExceptions
);
setEventListener(
"cameraSettingsButton",
"command",
@ -1684,6 +1689,22 @@ var gPrivacyPane = {
);
},
// XR
/**
* Displays the XR exceptions dialog where specific site XR
* preferences can be set.
*/
showXRExceptions() {
let params = { permissionType: "xr" };
gSubDialog.open(
"chrome://browser/content/preferences/sitePermissions.xhtml",
"resizable=yes",
params
);
},
// CAMERA
/**

Просмотреть файл

@ -25,6 +25,12 @@ const sitePermissionsL10n = {
disableLabel: "permissions-site-location-disable-label",
disableDescription: "permissions-site-location-disable-desc",
},
xr: {
window: "permissions-site-xr-window",
description: "permissions-site-xr-desc",
disableLabel: "permissions-site-xr-disable-label",
disableDescription: "permissions-site-xr-disable-desc",
},
camera: {
window: "permissions-site-camera-window",
description: "permissions-site-camera-desc",

Просмотреть файл

@ -3,6 +3,7 @@ tags = openwindow
support-files =
browser_privatebrowsing_concurrent_page.html
browser_privatebrowsing_geoprompt_page.html
browser_privatebrowsing_xrprompt_page.html
browser_privatebrowsing_localStorage_before_after_page.html
browser_privatebrowsing_localStorage_before_after_page2.html
browser_privatebrowsing_localStorage_page1.html
@ -35,8 +36,6 @@ skip-if = verify
[browser_privatebrowsing_downloadLastDir_c.js]
[browser_privatebrowsing_downloadLastDir_toggle.js]
[browser_privatebrowsing_favicon.js]
[browser_privatebrowsing_geoprompt.js]
tags = geolocation
[browser_privatebrowsing_lastpbcontextexited.js]
[browser_privatebrowsing_localStorage.js]
[browser_privatebrowsing_localStorage_before_after.js]
@ -47,6 +46,8 @@ tags = geolocation
[browser_privatebrowsing_placestitle.js]
[browser_privatebrowsing_popupblocker.js]
[browser_privatebrowsing_protocolhandler.js]
[browser_privatebrowsing_rememberprompt.js]
tags = geolocation xr
[browser_privatebrowsing_sidebar.js]
[browser_privatebrowsing_theming.js]
[browser_privatebrowsing_ui.js]

Просмотреть файл

@ -14,37 +14,29 @@ add_task(async function setup() {
});
add_task(async function test() {
const testPageURL =
"https://example.com/browser/" +
"browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html";
function checkGeolocation(aPrivateMode, aWindow) {
function checkPrompt(aURL, aName, aPrivateMode, aWindow) {
return (async function() {
aWindow.gBrowser.selectedTab = BrowserTestUtils.addTab(
aWindow.gBrowser,
testPageURL
aURL
);
await BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser);
let notification = aWindow.PopupNotifications.getNotification(
"geolocation"
);
let notification = aWindow.PopupNotifications.getNotification(aName);
// Wait until the notification is available.
while (!notification) {
await new Promise(resolve => {
executeSoon(resolve);
});
notification = aWindow.PopupNotifications.getNotification(
"geolocation"
);
notification = aWindow.PopupNotifications.getNotification(aName);
}
if (aPrivateMode) {
// Make sure the notification is correctly displayed without a remember control
ok(
!notification.options.checkbox.show,
"Secondary actions should exist (always/never remember)"
"Secondary actions should not exist (always/never remember)"
);
} else {
ok(
@ -58,23 +50,43 @@ add_task(async function test() {
})();
}
let win = await BrowserTestUtils.openNewBrowserWindow();
let browser = win.gBrowser.selectedBrowser;
BrowserTestUtils.loadURI(browser, testPageURL);
await BrowserTestUtils.browserLoaded(browser);
function checkPrivateBrowsingRememberPrompt(aURL, aName) {
return (async function() {
let win = await BrowserTestUtils.openNewBrowserWindow();
let browser = win.gBrowser.selectedBrowser;
BrowserTestUtils.loadURI(browser, aURL);
await BrowserTestUtils.browserLoaded(browser);
await checkGeolocation(false, win);
await checkPrompt(aURL, aName, false, win);
let privateWin = await BrowserTestUtils.openNewBrowserWindow({
private: true,
});
let privateBrowser = privateWin.gBrowser.selectedBrowser;
BrowserTestUtils.loadURI(privateBrowser, testPageURL);
await BrowserTestUtils.browserLoaded(privateBrowser);
let privateWin = await BrowserTestUtils.openNewBrowserWindow({
private: true,
});
let privateBrowser = privateWin.gBrowser.selectedBrowser;
BrowserTestUtils.loadURI(privateBrowser, aURL);
await BrowserTestUtils.browserLoaded(privateBrowser);
await checkGeolocation(true, privateWin);
await checkPrompt(aURL, aName, true, privateWin);
// Cleanup
await BrowserTestUtils.closeWindow(win);
await BrowserTestUtils.closeWindow(privateWin);
// Cleanup
await BrowserTestUtils.closeWindow(win);
await BrowserTestUtils.closeWindow(privateWin);
})();
}
const geoTestPageURL =
"https://example.com/browser/" +
"browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html";
await checkPrivateBrowsingRememberPrompt(geoTestPageURL, "geolocation");
const vrEnabled = Services.prefs.getBoolPref("dom.vr.enabled");
if (vrEnabled) {
const xrTestPageURL =
"https://example.com/browser/" +
"browser/components/privatebrowsing/test/browser/browser_privatebrowsing_xrprompt_page.html";
await checkPrivateBrowsingRememberPrompt(xrTestPageURL, "xr");
}
});

Просмотреть файл

@ -0,0 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>XR invoker</title>
</head>
<body>
<script type="text/javascript">
navigator.getVRDisplays();
</script>
</body>
</html>

Просмотреть файл

@ -57,6 +57,8 @@ urlbar-default-notification-anchor =
.tooltiptext = Open message panel
urlbar-geolocation-notification-anchor =
.tooltiptext = Open location request panel
urlbar-xr-notification-anchor =
.tooltiptext = Open virtual reality permission panel
urlbar-storage-access-anchor =
.tooltiptext = Open browsing activity permission panel
urlbar-translate-notification-anchor =
@ -84,6 +86,8 @@ urlbar-tip-help-icon =
urlbar-geolocation-blocked =
.tooltiptext = You have blocked location information for this website.
urlbar-xr-blocked =
.tooltiptext = You have blocked virtual reality device access for this website.
urlbar-web-notifications-blocked =
.tooltiptext = You have blocked notifications for this website.
urlbar-camera-blocked =

Просмотреть файл

@ -139,6 +139,16 @@ permissions-site-location-disable-label =
.label = Block new requests asking to access your location
permissions-site-location-disable-desc = This will prevent any websites not listed above from requesting permission to access your location. Blocking access to your location may break some website features.
## Site Permissions - Virtual Reality
permissions-site-xr-window =
.title = Settings - Virtual Reality Permissions
.style = { permissions-window.style }
permissions-site-xr-desc = The following websites have requested to access your virtual reality devices. You can specify which websites are allowed to access your virtual reality devices. You can also block new requests asking to access your virtual reality devices.
permissions-site-xr-disable-label =
.label = Block new requests asking to access your virtual reality devices
permissions-site-xr-disable-desc = This will prevent any websites not listed above from requesting permission to access your virtual reality devices. Blocking access to your virtual reality devices may break some website features.
## Site Permissions - Camera
permissions-site-camera-window =

Просмотреть файл

@ -1113,6 +1113,11 @@ permissions-location-settings =
.label = Settings…
.accesskey = t
permissions-xr = Virtual Reality
permissions-xr-settings =
.label = Settings…
.accesskey = t
permissions-camera = Camera
permissions-camera-settings =
.label = Settings…

Просмотреть файл

@ -628,6 +628,15 @@ geolocation.shareWithFile3=Will you allow this local file to access your locatio
geolocation.shareWithSiteUnsafeDelegation=Will you allow %1$S to give %2$S permission to access your location?
geolocation.remember=Remember this decision
# Virtual Reality Device UI
xr.allow=Allow Virtual Reality Access
xr.allow.accesskey=A
xr.dontAllow=Dont Allow
xr.dontAllow.accesskey=n
xr.shareWithSite3=Will you allow %S to access virtual reality devices? This may expose sensitive information.
xr.shareWithFile3=Will you allow this local file to access virtual reality devices? This may expose sensitive information.
xr.remember=Remember this decision
# Persistent storage UI
persistentStorage.allow=Allow
persistentStorage.allow.accesskey=A

Просмотреть файл

@ -40,6 +40,7 @@ permission.screen.label = Share the Screen
permission.install.label = Install Add-ons
permission.popup.label = Open Pop-up Windows
permission.geo.label = Access Your Location
permission.xr.label = Access Virtual Reality Devices
permission.shortcuts.label = Override Keyboard Shortcuts
permission.focus-tab-by-prompt.label = Switch to this Tab
permission.persistent-storage.label = Store Data in Persistent Storage

Просмотреть файл

@ -833,6 +833,111 @@ GeolocationPermissionPrompt.prototype = {
PermissionUI.GeolocationPermissionPrompt = GeolocationPermissionPrompt;
/**
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
* the WebXR API.
*
* @param request (nsIContentPermissionRequest)
* The request for a permission from content.
*/
function XRPermissionPrompt(request) {
this.request = request;
}
XRPermissionPrompt.prototype = {
__proto__: PermissionPromptForRequestPrototype,
get type() {
return "xr";
},
get permissionKey() {
return "xr";
},
get popupOptions() {
let pref = "browser.xr.warning.infoURL";
let options = {
learnMoreURL: Services.urlFormatter.formatURLPref(pref),
displayURI: false,
name: this.getPrincipalName(),
};
if (this.principal.URI.schemeIs("file")) {
options.checkbox = { show: false };
} else {
// Don't offer "always remember" action in PB mode
options.checkbox = {
show: !PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal),
};
}
if (options.checkbox.show) {
options.checkbox.label = gBrowserBundle.GetStringFromName("xr.remember");
}
return options;
},
get notificationID() {
return "xr";
},
get anchorID() {
return "xr-notification-icon";
},
get message() {
if (this.principal.URI.schemeIs("file")) {
return gBrowserBundle.GetStringFromName("xr.shareWithFile3");
}
return gBrowserBundle.formatStringFromName("xr.shareWithSite3", ["<>"]);
},
get promptActions() {
return [
{
label: gBrowserBundle.GetStringFromName("xr.allow"),
accessKey: gBrowserBundle.GetStringFromName("xr.allow.accesskey"),
action: SitePermissions.ALLOW,
},
{
label: gBrowserBundle.GetStringFromName("xr.dontAllow"),
accessKey: gBrowserBundle.GetStringFromName("xr.dontAllow.accesskey"),
action: SitePermissions.BLOCK,
},
];
},
_updateXRSharing(state) {
let gBrowser = this.browser.ownerGlobal.gBrowser;
if (gBrowser == null) {
return;
}
gBrowser.updateBrowserSharing(this.browser, { xr: state });
let devicePermOrigins = this.browser.getDevicePermissionOrigins("xr");
if (!state) {
devicePermOrigins.delete(this.principal.origin);
return;
}
devicePermOrigins.add(this.principal.origin);
},
allow(...args) {
this._updateXRSharing(true);
PermissionPromptForRequestPrototype.allow.apply(this, args);
},
cancel(...args) {
this._updateXRSharing(false);
PermissionPromptForRequestPrototype.cancel.apply(this, args);
},
};
PermissionUI.XRPermissionPrompt = XRPermissionPrompt;
/**
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
* the Desktop Notification API.

Просмотреть файл

@ -966,6 +966,10 @@ var gPermissionObject = {
exactHostMatch: true,
},
xr: {
exactHostMatch: true,
},
"focus-tab-by-prompt": {
exactHostMatch: true,
states: [SitePermissions.UNKNOWN, SitePermissions.ALLOW],

Просмотреть файл

@ -18,6 +18,11 @@ add_task(async function test_geo_permission_prompt() {
await testPrompt(PermissionUI.GeolocationPermissionPrompt);
});
// Tests that XRPermissionPrompt works as expected
add_task(async function test_xr_permission_prompt() {
await testPrompt(PermissionUI.XRPermissionPrompt);
});
// Tests that DesktopNotificationPermissionPrompt works as expected
add_task(async function test_desktop_notification_permission_prompt() {
Services.prefs.setBoolPref(

Просмотреть файл

@ -59,6 +59,8 @@ add_task(async function testGetAllPermissionDetailsForBrowser() {
SitePermissions.ALLOW
);
SitePermissions.setForPrincipal(principal, "xr", SitePermissions.ALLOW);
let permissions = SitePermissions.getAllPermissionDetailsForBrowser(
tab.linkedBrowser
);
@ -112,11 +114,21 @@ add_task(async function testGetAllPermissionDetailsForBrowser() {
scope: SitePermissions.SCOPE_PERSISTENT,
});
let xr = permissions.find(({ id }) => id === "xr");
Assert.deepEqual(xr, {
id: "xr",
label: "Access Virtual Reality Devices",
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_PERSISTENT,
});
SitePermissions.removeFromPrincipal(principal, "cookie");
SitePermissions.removeFromPrincipal(principal, "popup");
SitePermissions.removeFromPrincipal(principal, "geo");
SitePermissions.removeFromPrincipal(principal, "shortcuts");
SitePermissions.removeFromPrincipal(principal, "xr");
Services.prefs.clearUserPref("permissions.default.shortcuts");
BrowserTestUtils.removeTab(gBrowser.selectedTab);

Просмотреть файл

@ -28,6 +28,7 @@ add_task(async function testPermissionsListing() {
"shortcuts",
"persistent-storage",
"storage-access",
"xr",
];
if (RESIST_FINGERPRINTING_ENABLED) {
// Canvas permission should be hidden unless privacy.resistFingerprinting
@ -184,6 +185,7 @@ add_task(async function testExactHostMatch() {
"microphone",
"screen",
"geo",
"xr",
"persistent-storage",
];
if (RESIST_FINGERPRINTING_ENABLED) {

Просмотреть файл

@ -69,6 +69,10 @@
list-style-image: url(chrome://browser/skin/notification-icons/geo.svg);
}
.xr-icon {
list-style-image: url(chrome://browser/skin/notification-icons/xr.svg);
}
.camera-icon {
list-style-image: url(chrome://browser/skin/notification-icons/camera.svg);
}

Просмотреть файл

@ -91,6 +91,8 @@
skin/classic/browser/notification-icons/popup-subitem.svg (../shared/notification-icons/popup-subitem.svg)
skin/classic/browser/notification-icons/screen-blocked.svg (../shared/notification-icons/screen-blocked.svg)
skin/classic/browser/notification-icons/screen.svg (../shared/notification-icons/screen.svg)
skin/classic/browser/notification-icons/xr-blocked.svg (../shared/notification-icons/xr-blocked.svg)
skin/classic/browser/notification-icons/xr.svg (../shared/notification-icons/xr.svg)
skin/classic/browser/notification-icons/update.svg (../shared/notification-icons/update.svg)
skin/classic/browser/notification-icons/midi.svg (../shared/notification-icons/midi.svg)
skin/classic/browser/notification-icons/webauthn.svg (../shared/notification-icons/webauthn.svg)

Просмотреть файл

@ -70,6 +70,18 @@
list-style-image: url(chrome://browser/skin/notification-icons/geo-detailed.svg);
}
.xr-icon {
list-style-image: url(chrome://browser/skin/notification-icons/xr.svg);
}
.xr-icon.blocked-permission-icon {
list-style-image: url(chrome://browser/skin/notification-icons/xr-blocked.svg);
}
.popup-notification-icon[popupid="xr"] {
list-style-image: url(chrome://browser/skin/notification-icons/xr.svg);
}
.autoplay-media-icon {
list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media.svg);
}

Просмотреть файл

@ -0,0 +1,10 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
<path d="M14.3,4.3l-2.6,2.6c0.6,0.3,0.9,1,0.6,1.6c-0.3,0.6-1,0.9-1.6,0.6c-0.2-0.1-0.4-0.3-0.6-0.6l-1.7,1.7L9,11.5
c0.1,0.1,0.2,0.2,0.3,0.2h4.8c0.6,0,1-0.5,1-1V5.3C15.1,4.8,14.8,4.4,14.3,4.3z"/>
<path d="M12.8,4.4c0.3-0.3,0.2-0.7,0-0.9c-0.3-0.2-0.7-0.2-0.9,0l-0.8,0.8H2c-0.6,0-1,0.5-1,1v5.4c0,0.6,0.5,1,1,1h1.7
c-0.2,0.3,0,0.7,0.3,0.9c0.3,0.1,0.6,0.1,0.8-0.1L12.8,4.4z M3.7,8.5c-0.3-0.6,0-1.3,0.6-1.6c0.6-0.3,1.3,0,1.6,0.6s0,1.3-0.6,1.6
C5.1,9.2,5,9.2,4.8,9.2C4.3,9.2,3.9,8.9,3.7,8.5z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 841 B

Просмотреть файл

@ -0,0 +1,9 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
<path d="M14.1,4.3H2c-0.6,0-1,0.5-1,1v5.4c0,0.6,0.5,1,1,1h4.7c0.1,0,0.2-0.1,0.3-0.2l0.7-1.4C7.8,9.9,8,9.9,8.1,9.9
c0.1,0,0.1,0.1,0.2,0.2L9,11.5c0.1,0.1,0.2,0.2,0.3,0.2h4.8c0.6,0,1-0.5,1-1V5.3C15.1,4.7,14.6,4.3,14.1,4.3z M4.8,9.2
C4.1,9.2,3.6,8.7,3.6,8c0-0.7,0.5-1.2,1.2-1.2C5.5,6.8,6,7.3,6,8c0,0,0,0,0,0C6,8.7,5.5,9.2,4.8,9.2z M11.2,9.2
C10.5,9.2,10,8.7,10,8c0-0.7,0.5-1.2,1.2-1.2c0.7,0,1.2,0.5,1.2,1.2c0,0,0,0,0,0C12.4,8.7,11.9,9.2,11.2,9.2z"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 798 B

Просмотреть файл

@ -10,6 +10,7 @@
</head>
<body>
<button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
<button id="xr" onclick="content.navigator.getVRDisplays();">WebXR</button>
<button id="persistent-storage" onclick="navigator.storage.persist()">Persistent Storage</button>
<button id="webRTC-shareDevices" onclick="shareDevice({video: true, fake: true});">Video</button>
<button id="webRTC-shareMicrophone" onclick="shareDevice({audio: true, fake: true});">Audio</button>

Просмотреть файл

@ -1480,9 +1480,6 @@ already_AddRefed<Promise> Navigator::GetVRDisplays(ErrorResult& aRv) {
return nullptr;
}
nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
win->NotifyVREventListenerAdded();
RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
@ -1510,39 +1507,53 @@ already_AddRefed<Promise> Navigator::GetVRDisplays(ErrorResult& aRv) {
}
void Navigator::FinishGetVRDisplays(bool isWebVRSupportedInwindow, Promise* p) {
if (isWebVRSupportedInwindow) {
nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
// Since FinishGetVRDisplays can be called asynchronously after an IPC
// response, it's possible that the Window can be torn down before this
// call. In that case, the Window's cyclic references to VR objects are
// also torn down and should not be recreated via
// NotifyVREventListenerAdded.
if (!win->IsDying()) {
win->NotifyVREventListenerAdded();
// We pass mWindow's id to RefreshVRDisplays, so
// NotifyVRDisplaysUpdated will be called asynchronously, resolving
// the promises in mVRGetDisplaysPromises.
if (!VRDisplay::RefreshVRDisplays(win->WindowID())) {
// Failed to refresh, reject the promise now
p->MaybeRejectWithTypeError(u"Failed to find attached VR displays.");
} else {
// Succeeded, so cache the promise to resolve later
mVRGetDisplaysPromises.AppendElement(p);
}
} else {
// The Window has been torn down, so there is no further work that can
// be done.
p->MaybeRejectWithTypeError(
u"Unable to return VRDisplays for a closed window.");
}
} else {
if (!isWebVRSupportedInwindow) {
// WebVR in this window is not supported, so resolve the promise
// with no displays available
nsTArray<RefPtr<VRDisplay>> vrDisplaysEmpty;
p->MaybeResolve(vrDisplaysEmpty);
return;
}
// Since FinishGetVRDisplays can be called asynchronously after an IPC
// response, it's possible that the Window can be torn down before this
// call. In that case, the Window's cyclic references to VR objects are
// also torn down and should not be recreated via
// NotifyVREventListenerAdded.
nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
if (win->IsDying()) {
// The Window has been torn down, so there is no further work that can
// be done.
p->MaybeRejectWithTypeError(
u"Unable to return VRDisplays for a closed window.");
return;
}
mVRGetDisplaysPromises.AppendElement(p);
win->RequestXRPermission();
}
void Navigator::OnXRPermissionRequestAllow() {
nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(mWindow);
// We pass mWindow's id to RefreshVRDisplays, so NotifyVRDisplaysUpdated will
// be called asynchronously, resolving the promises in mVRGetDisplaysPromises.
if (!VRDisplay::RefreshVRDisplays(win->WindowID())) {
for (auto& p : mVRGetDisplaysPromises) {
// Failed to refresh, reject the promise now
p->MaybeRejectWithTypeError(u"Failed to find attached VR displays.");
}
}
}
void Navigator::OnXRPermissionRequestCancel() {
nsTArray<RefPtr<VRDisplay>> vrDisplays;
for (auto& p : mVRGetDisplaysPromises) {
// Resolve the promise with no vr displays when
// the user blocks access.
p->MaybeResolve(vrDisplays);
}
mVRGetDisplaysPromises.Clear();
}
void Navigator::GetActiveVRDisplays(

Просмотреть файл

@ -181,6 +181,8 @@ class Navigator final : public nsISupports, public nsWrapperCache {
already_AddRefed<Promise> GetVRDisplays(ErrorResult& aRv);
void FinishGetVRDisplays(bool isWebVRSupportedInwindow, Promise* p);
void GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const;
void OnXRPermissionRequestAllow();
void OnXRPermissionRequestCancel();
VRServiceTest* RequestVRServiceTest();
bool IsWebVRContentDetected() const;
bool IsWebVRContentPresenting() const;

Просмотреть файл

@ -187,6 +187,7 @@
#include "mozilla/dom/VRDisplayEvent.h"
#include "mozilla/dom/VRDisplayEventBinding.h"
#include "mozilla/dom/VREventObserver.h"
#include "mozilla/dom/XRPermissionRequest.h"
#include "nsRefreshDriver.h"
#include "Layers.h"
@ -839,6 +840,8 @@ nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow,
mHasGamepad(false),
mHasVREvents(false),
mHasVRDisplayActivateEvents(false),
mXRPermissionRequestInFlight(false),
mXRPermissionGranted(false),
mHasSeenGamepadInput(false),
mSuspendDepth(0),
mFreezeDepth(0),
@ -1139,6 +1142,8 @@ void nsGlobalWindowInner::FreeInnerObjects() {
DisableVRUpdates();
mHasVREvents = false;
mHasVRDisplayActivateEvents = false;
mXRPermissionRequestInFlight = false;
mXRPermissionGranted = false;
mVRDisplays.Clear();
// This breaks a cycle between the window and the ClientSource object.
@ -6014,13 +6019,48 @@ void nsGlobalWindowInner::SetHasGamepadEventListener(
}
}
void nsGlobalWindowInner::RequestXRPermission() {
if (mXRPermissionGranted) {
// Don't prompt redundantly once permission to
// access XR devices has been granted.
OnXRPermissionRequestAllow();
return;
}
if (mXRPermissionRequestInFlight) {
// Don't allow multiple simultaneous permissions requests;
return;
}
mXRPermissionRequestInFlight = true;
RefPtr<XRPermissionRequest> request =
new XRPermissionRequest(this, WindowID());
Unused << NS_WARN_IF(NS_FAILED(request->Start()));
}
void nsGlobalWindowInner::OnXRPermissionRequestAllow() {
mXRPermissionRequestInFlight = false;
mXRPermissionGranted = true;
NotifyVREventListenerAdded();
dom::Navigator* nav = Navigator();
MOZ_ASSERT(nav != nullptr);
nav->OnXRPermissionRequestAllow();
}
void nsGlobalWindowInner::OnXRPermissionRequestCancel() {
mXRPermissionRequestInFlight = false;
dom::Navigator* nav = Navigator();
MOZ_ASSERT(nav != nullptr);
nav->OnXRPermissionRequestCancel();
}
void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
if (aType == nsGkAtoms::onvrdisplayactivate ||
aType == nsGkAtoms::onvrdisplayconnect ||
aType == nsGkAtoms::onvrdisplaydeactivate ||
aType == nsGkAtoms::onvrdisplaydisconnect ||
aType == nsGkAtoms::onvrdisplaypresentchange) {
NotifyVREventListenerAdded();
RequestXRPermission();
}
if (aType == nsGkAtoms::onvrdisplayactivate) {

Просмотреть файл

@ -371,6 +371,9 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
bool HasUsedVR() const;
bool IsVRContentDetected() const;
bool IsVRContentPresenting() const;
void RequestXRPermission();
void OnXRPermissionRequestAllow();
void OnXRPermissionRequestCancel();
using EventTarget::EventListenerAdded;
virtual void EventListenerAdded(nsAtom* aType) override;
@ -1285,6 +1288,15 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
// Indicates whether this window wants VRDisplayActivate events
bool mHasVRDisplayActivateEvents : 1;
// Indicates that an XR permission request has been requested
// but has not yet been resolved.
bool mXRPermissionRequestInFlight : 1;
// Indicates that an XR permission request has been granted.
// The page should not request permission multiple times.
bool mXRPermissionGranted : 1;
nsCheapSet<nsUint32HashKey> mGamepadIndexSet;
nsRefPtrHashtable<nsUint32HashKey, mozilla::dom::Gamepad> mGamepads;
bool mHasSeenGamepadInput;

Просмотреть файл

@ -1296,43 +1296,37 @@ var interfaceNamesInGlobalScope = [
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "VisualViewport", insecureContext: true },
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "VRDisplay", insecureContext: true, releaseNonWindowsAndMac: false },
{ name: "VRDisplay", releaseNonWindowsAndMac: false },
// IMPORTANT: Do not change this list without review from a DOM peer!
{
name: "VRDisplayCapabilities",
insecureContext: true,
releaseNonWindowsAndMac: false,
},
// IMPORTANT: Do not change this list without review from a DOM peer!
{
name: "VRDisplayEvent",
insecureContext: true,
releaseNonWindowsAndMac: false,
},
// IMPORTANT: Do not change this list without review from a DOM peer!
{
name: "VREyeParameters",
insecureContext: true,
releaseNonWindowsAndMac: false,
},
// IMPORTANT: Do not change this list without review from a DOM peer!
{
name: "VRFieldOfView",
insecureContext: true,
releaseNonWindowsAndMac: false,
},
// IMPORTANT: Do not change this list without review from a DOM peer!
{
name: "VRFrameData",
insecureContext: true,
releaseNonWindowsAndMac: false,
},
// IMPORTANT: Do not change this list without review from a DOM peer!
{ name: "VRPose", insecureContext: true, releaseNonWindowsAndMac: false },
{ name: "VRPose", releaseNonWindowsAndMac: false },
// IMPORTANT: Do not change this list without review from a DOM peer!
{
name: "VRStageParameters",
insecureContext: true,
releaseNonWindowsAndMac: false,
},
// IMPORTANT: Do not change this list without review from a DOM peer!

Просмотреть файл

@ -0,0 +1,74 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#include "XRPermissionRequest.h"
#include "nsIGlobalObject.h"
#include "mozilla/Preferences.h"
#include "nsContentUtils.h"
namespace mozilla {
namespace dom {
//-------------------------------------------------
// XR Permission Requests
//-------------------------------------------------
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(XRPermissionRequest,
ContentPermissionRequestBase)
NS_IMPL_CYCLE_COLLECTION_INHERITED(XRPermissionRequest,
ContentPermissionRequestBase)
XRPermissionRequest::XRPermissionRequest(nsPIDOMWindowInner* aWindow,
uint64_t aWindowId)
: ContentPermissionRequestBase(aWindow->GetDoc()->NodePrincipal(), aWindow,
NS_LITERAL_CSTRING("dom.vr"),
NS_LITERAL_CSTRING("xr")),
mWindowId(aWindowId) {
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aWindow->GetDoc());
mPrincipal = aWindow->GetDoc()->NodePrincipal();
MOZ_ASSERT(mPrincipal);
}
NS_IMETHODIMP
XRPermissionRequest::Cancel() {
nsGlobalWindowInner* window =
nsGlobalWindowInner::GetInnerWindowWithId(mWindowId);
if (!window) {
return NS_OK;
}
window->OnXRPermissionRequestCancel();
return NS_OK;
}
NS_IMETHODIMP
XRPermissionRequest::Allow(JS::HandleValue aChoices) {
MOZ_ASSERT(aChoices.isUndefined());
nsGlobalWindowInner* window =
nsGlobalWindowInner::GetInnerWindowWithId(mWindowId);
if (!window) {
return NS_OK;
}
window->OnXRPermissionRequestAllow();
return NS_OK;
}
nsresult XRPermissionRequest::Start() {
MOZ_ASSERT(NS_IsMainThread());
PromptResult pr = CheckPromptPrefs();
if (pr == PromptResult::Granted) {
return Allow(JS::UndefinedHandleValue);
}
if (pr == PromptResult::Denied) {
return Cancel();
}
return nsContentPermissionUtils::AskPermission(this, mWindow);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef mozilla_dom_XRPermissionRequest_h_
#define mozilla_dom_XRPermissionRequest_h_
#include "mozilla/dom/Promise.h"
#include "nsContentPermissionHelper.h"
#include "nsISupports.h"
namespace mozilla {
namespace dom {
/**
* Handles permission dialog management when requesting XR device access.
*/
class XRPermissionRequest final : public ContentPermissionRequestBase {
public:
XRPermissionRequest(nsPIDOMWindowInner* aWindow, uint64_t aWindowId);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XRPermissionRequest,
ContentPermissionRequestBase)
// nsIContentPermissionRequest
NS_IMETHOD Cancel(void) override;
NS_IMETHOD Allow(JS::HandleValue choices) override;
nsresult Start();
private:
~XRPermissionRequest() = default;
uint64_t mWindowId;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_XR_h_

Просмотреть файл

@ -11,14 +11,16 @@ EXPORTS.mozilla.dom += [
'VRDisplay.h',
'VRDisplayEvent.h',
'VREventObserver.h',
'VRServiceTest.h'
'VRServiceTest.h',
'XRPermissionRequest.h',
]
UNIFIED_SOURCES = [
'VRDisplay.cpp',
'VRDisplayEvent.cpp',
'VREventObserver.cpp',
'VRServiceTest.cpp'
'VRServiceTest.cpp',
'XRPermissionRequest.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')

Просмотреть файл

@ -213,11 +213,12 @@ partial interface Navigator {
GamepadServiceTest requestGamepadServiceTest();
};
// https://immersive-web.github.io/webvr/spec/1.1/#interface-navigator
partial interface Navigator {
[Throws, Pref="dom.vr.enabled"]
[Throws, SecureContext, Pref="dom.vr.enabled"]
Promise<sequence<VRDisplay>> getVRDisplays();
// TODO: Use FrozenArray once available. (Bug 1236777)
[Frozen, Cached, Pure, Pref="dom.vr.enabled"]
[SecureContext, Frozen, Cached, Pure, Pref="dom.vr.enabled"]
readonly attribute sequence<VRDisplay> activeVRDisplays;
[ChromeOnly, Pref="dom.vr.enabled"]
readonly attribute boolean isWebVRContentDetected;

Просмотреть файл

@ -1,7 +1,11 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* https://immersive-web.github.io/webvr/spec/1.1/
*/
enum VREye {
"left",
@ -10,6 +14,7 @@ enum VREye {
[Pref="dom.vr.enabled",
HeaderFile="mozilla/dom/VRDisplay.h",
SecureContext,
Exposed=Window]
interface VRFieldOfView {
readonly attribute double upDegrees;
@ -51,6 +56,7 @@ dictionary VRLayer {
*/
[Pref="dom.vr.enabled",
HeaderFile="mozilla/dom/VRDisplay.h",
SecureContext,
Exposed=Window]
interface VRDisplayCapabilities {
/**
@ -93,6 +99,7 @@ interface VRDisplayCapabilities {
*/
[Pref="dom.vr.enabled",
HeaderFile="mozilla/dom/VRDisplay.h",
SecureContext,
Exposed=Window]
interface VRStageParameters {
/**
@ -119,6 +126,7 @@ interface VRStageParameters {
[Pref="dom.vr.enabled",
HeaderFile="mozilla/dom/VRDisplay.h",
SecureContext,
Exposed=Window]
interface VRPose
{
@ -140,6 +148,7 @@ interface VRPose
[Pref="dom.vr.enabled",
HeaderFile="mozilla/dom/VRDisplay.h",
SecureContext,
Exposed=Window]
interface VRFrameData {
constructor();
@ -157,6 +166,7 @@ interface VRFrameData {
[Pref="dom.vr.enabled",
HeaderFile="mozilla/dom/VRDisplay.h",
SecureContext,
Exposed=Window]
interface VREyeParameters {
/**
@ -181,6 +191,7 @@ interface VREyeParameters {
[Pref="dom.vr.enabled",
HeaderFile="mozilla/dom/VRDisplay.h",
SecureContext,
Exposed=Window]
interface VRDisplay : EventTarget {
/**

Просмотреть файл

@ -16,6 +16,7 @@ dictionary VRDisplayEventInit : EventInit {
};
[Pref="dom.vr.enabled",
SecureContext,
Exposed=Window]
interface VRDisplayEvent : Event {
constructor(DOMString type, VRDisplayEventInit eventInitDict);

Просмотреть файл

@ -38,6 +38,7 @@ static const DelegateInfo sPermissionsMap[] = {
{"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
{"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},
{"screen", u"display-capture", DelegatePolicy::eDelegateUseFeaturePolicy},
{"xr", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
};
NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler)

Просмотреть файл

@ -1 +1,3 @@
prefs: [dom.vr.enabled:true, dom.security.featurePolicy.enabled:true, dom.security.featurePolicy.experimental.enabled:true, dom.security.featurePolicy.header.enabled:true, dom.security.featurePolicy.webidl.enabled:true]
prefs: [dom.vr.enabled:true,
dom.vr.prompt.testing:true, dom.vr.prompt.testing.allow:true, dom.security.featurePolicy.experimental.enabled:true,
dom.security.featurePolicy.enabled:true, dom.security.featurePolicy.header.enabled:true, dom.security.featurePolicy.webidl.enabled:true]