diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index b329cbfab5ed..ed8a10173e5a 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -966,7 +966,15 @@ var gIdentityHandler = {
let hasBlockedPopupIndicator = false;
for (let permission of permissions) {
+ if (permission.id == "storage-access") {
+ // Ignore storage access permissions here, they are made visible inside
+ // the Content Blocking UI.
+ continue;
+ }
let item = this._createPermissionItem(permission);
+ if (!item) {
+ continue;
+ }
this._permissionList.appendChild(item);
if (permission.id == "popup" &&
@@ -1035,7 +1043,11 @@ var gIdentityHandler = {
let nameLabel = document.createXULElement("label");
nameLabel.setAttribute("flex", "1");
nameLabel.setAttribute("class", "identity-popup-permission-label");
- nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
+ let label = SitePermissions.getPermissionLabel(aPermission.id);
+ if (label === null) {
+ return null;
+ }
+ nameLabel.textContent = label;
let nameLabelId = "identity-popup-permission-label-" + aPermission.id;
nameLabel.setAttribute("id", nameLabelId);
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
index 41c8150d0f20..d42a4f6ece04 100644
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -977,6 +977,8 @@ xmlns="http://www.w3.org/1999/xhtml"
tooltiptext="&urlbar.midiNotificationAnchor.tooltip;"/>
+
diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js
index 58666ab74203..15b45c02e69c 100644
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -12,11 +12,13 @@ var gPermPrincipal;
var gUsageRequest;
// Array of permissionIDs sorted alphabetically by label.
-var gPermissions = SitePermissions.listPermissions().sort((a, b) => {
- let firstLabel = SitePermissions.getPermissionLabel(a);
- let secondLabel = SitePermissions.getPermissionLabel(b);
- return firstLabel.localeCompare(secondLabel);
-});
+var gPermissions = SitePermissions.listPermissions()
+ .filter(p => SitePermissions.getPermissionLabel(p) != null)
+ .sort((a, b) => {
+ let firstLabel = SitePermissions.getPermissionLabel(a);
+ let secondLabel = SitePermissions.getPermissionLabel(b);
+ return firstLabel.localeCompare(secondLabel);
+ });
var permissionObserver = {
observe(aSubject, aTopic, aData) {
diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc
index b943d33110cf..e16207367b70 100644
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -103,3 +103,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js
index e523748c7130..f0cf94f621d4 100644
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -3135,6 +3135,9 @@ const ContentPermissionIntegration = {
case "autoplay-media": {
return new PermissionUI.AutoplayPermissionPrompt(request);
}
+ case "storage-access": {
+ return new PermissionUI.StorageAccessPermissionPrompt(request);
+ }
}
return undefined;
},
diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd
index 37f8c1d90627..e106ce7ff53b 100644
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -282,6 +282,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
+
diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties
index 6955d249c7de..680ff75fa9ea 100644
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -965,6 +965,24 @@ autoplay.messageWithFile = Will you allow this file to autoplay media with sound
# popup panels, including the sliding subviews of the main menu.
panel.back = Back
+storageAccess.Allow.label = Allow Access
+storageAccess.Allow.accesskey = A
+storageAccess.AllowOnAnySite.label = Allow access on any site
+storageAccess.AllowOnAnySite.accesskey = w
+storageAccess.DontAllow.label = Block Access
+storageAccess.DontAllow.accesskey = B
+# LOCALIZATION NOTE (storageAccess.message):
+# %1$S is the name of the site URL (www.site1.example) trying to track the user's activity.
+# %2$S is the name of the site URL (www.site2.example) that the user is visiting. This is the same domain name displayed in the address bar.
+storageAccess.message = Will you give %1$S access to track your browsing activity on %2$S?
+# LOCALIZATION NOTE (storageAccess.description.label):
+# %1$S is the name of the site URL (www.site1.example) trying to track the user's activity.
+# %2$S will be replaced with the localized version of storageAccess.description.learnmore. This text will be converted into a hyper-link linking to the SUMO page explaining the concept of third-party trackers.
+storageAccess.description.label = You may want to block %1$S on this site if you don’t recognize or trust it. Learn more about %2$S
+# LOCALIZATION NOTE (storageAccess.description.learnmore):
+# The value of this string is embedded inside storageAccess.description.label. See the localization note for storageAccess.description.label.
+storageAccess.description.learnmore = third-party trackers
+
confirmationHint.sendToDevice.label = Sent!
confirmationHint.sendToDeviceOffline.label = Queued (offline)
confirmationHint.copyURL.label = Copied to clipboard!
diff --git a/browser/locales/en-US/chrome/browser/sitePermissions.properties b/browser/locales/en-US/chrome/browser/sitePermissions.properties
index c6371f3baf1c..2f17abec4d34 100644
--- a/browser/locales/en-US/chrome/browser/sitePermissions.properties
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.properties
@@ -44,4 +44,4 @@ permission.persistent-storage.label = Store Data in Persistent Storage
permission.canvas.label = Extract Canvas Data
permission.flash-plugin.label = Run Adobe Flash
permission.midi.label = Access MIDI Devices
-permission.midi-sysex.label = Access MIDI Devices with SysEx Support
\ No newline at end of file
+permission.midi-sysex.label = Access MIDI Devices with SysEx Support
diff --git a/browser/modules/PermissionUI.jsm b/browser/modules/PermissionUI.jsm
index 8bd9767af339..b84d02fb2970 100644
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -257,8 +257,14 @@ var PermissionPromptPrototype = {
onBeforeShow() {},
/**
- * If the prompt was be shown to the user, this callback will
- * be called just after its been hidden.
+ * If the prompt was shown to the user, this callback will be called just
+ * after it's been shown.
+ */
+ onShown() {},
+
+ /**
+ * If the prompt was shown to the user, this callback will be called just
+ * after it's been hidden.
*/
onAfterShow() {},
@@ -423,6 +429,10 @@ var PermissionPromptPrototype = {
if (topic == "swapping") {
return true;
}
+ // The prompt has been shown, notify the PermissionUI.
+ if (topic == "shown") {
+ this.onShown();
+ }
// The prompt has been removed, notify the PermissionUI.
if (topic == "removed") {
this.onAfterShow();
@@ -472,8 +482,8 @@ var PermissionPromptForRequestPrototype = {
this.request.cancel();
},
- allow() {
- this.request.allow();
+ allow(choices) {
+ this.request.allow(choices);
},
};
@@ -905,3 +915,99 @@ AutoplayPermissionPrompt.prototype = {
};
PermissionUI.AutoplayPermissionPrompt = AutoplayPermissionPrompt;
+
+function StorageAccessPermissionPrompt(request) {
+ this.request = request;
+}
+
+StorageAccessPermissionPrompt.prototype = {
+ __proto__: PermissionPromptForRequestPrototype,
+
+ get usePermissionManager() {
+ return false;
+ },
+
+ get permissionKey() {
+ // Make sure this name is unique per each third-party tracker
+ return "storage-access-" + this.principal.origin;
+ },
+
+ get popupOptions() {
+ return {
+ displayURI: false,
+ name: this.principal.URI.hostPort,
+ secondName: this.topLevelPrincipal.URI.hostPort,
+ };
+ },
+
+ onShown() {
+ let document = this.browser.ownerDocument;
+ let label =
+ gBrowserBundle.formatStringFromName("storageAccess.description.label",
+ [this.request.principal.URI.hostPort, "<>"], 2);
+ let parts = label.split("<>");
+ if (parts.length == 1) {
+ parts.push("");
+ }
+ let map = {
+ "storage-access-perm-label": parts[0],
+ "storage-access-perm-learnmore":
+ gBrowserBundle.GetStringFromName("storageAccess.description.learnmore"),
+ "storage-access-perm-endlabel": parts[1],
+ };
+ for (let id in map) {
+ let str = map[id];
+ document.getElementById(id).textContent = str;
+ }
+ let learnMoreURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") + "third-party-cookies";
+ document.getElementById("storage-access-perm-learnmore")
+ .href = learnMoreURL;
+ },
+
+ get notificationID() {
+ return "storage-access";
+ },
+
+ get anchorID() {
+ return "storage-access-notification-icon";
+ },
+
+ get message() {
+ return gBrowserBundle.formatStringFromName("storageAccess.message", ["<>", "<>"], 2);
+ },
+
+ get promptActions() {
+ let self = this;
+ return [{
+ label: gBrowserBundle.GetStringFromName("storageAccess.DontAllow.label"),
+ accessKey: gBrowserBundle.GetStringFromName("storageAccess.DontAllow.accesskey"),
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ callback(state) {
+ self.cancel();
+ },
+ },
+ {
+ label: gBrowserBundle.GetStringFromName("storageAccess.Allow.label"),
+ accessKey: gBrowserBundle.GetStringFromName("storageAccess.Allow.accesskey"),
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ callback(state) {
+ self.allow({"storage-access": "allow"});
+ },
+ },
+ {
+ label: gBrowserBundle.GetStringFromName("storageAccess.AllowOnAnySite.label"),
+ accessKey: gBrowserBundle.GetStringFromName("storageAccess.AllowOnAnySite.accesskey"),
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ callback(state) {
+ self.allow({"storage-access": "allow-on-any-site"});
+ },
+ }];
+ },
+
+ get topLevelPrincipal() {
+ return this.request.topLevelPrincipal;
+ },
+};
+
+PermissionUI.StorageAccessPermissionPrompt = StorageAccessPermissionPrompt;
diff --git a/browser/modules/SitePermissions.jsm b/browser/modules/SitePermissions.jsm
index 003635a3fb4f..3e72127843c6 100644
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -355,7 +355,7 @@ var SitePermissions = {
* (e.g. SitePermissions.ALLOW)
* - scope: a constant representing how long the permission will
* be kept.
- * - label: the localized label
+ * - label: the localized label, or null if none is available.
*/
getAllPermissionDetailsForBrowser(browser) {
return this.getAllForBrowser(browser).map(({id, scope, state}) =>
@@ -653,9 +653,18 @@ var SitePermissions = {
* @param {string} permissionID
* The permission to get the label for.
*
- * @return {String} the localized label.
+ * @return {String} the localized label or null if none is available.
*/
getPermissionLabel(permissionID) {
+ if (!(permissionID in gPermissionObject)) {
+ // Permission can't be found.
+ return null;
+ }
+ if ("labelID" in gPermissionObject[permissionID] &&
+ gPermissionObject[permissionID].labelID === null) {
+ // Permission doesn't support having a label.
+ return null;
+ }
let labelID = gPermissionObject[permissionID].labelID || permissionID;
return gStringBundle.GetStringFromName("permission." + labelID + ".label");
},
@@ -852,6 +861,13 @@ var gPermissionObject = {
"midi-sysex": {
exactHostMatch: true,
},
+
+ "storage-access": {
+ labelID: null,
+ getDefault() {
+ return SitePermissions.UNKNOWN;
+ },
+ },
};
if (!Services.prefs.getBoolPref("dom.webmidi.enabled")) {
diff --git a/browser/modules/test/unit/test_SitePermissions.js b/browser/modules/test/unit/test_SitePermissions.js
index 10ead30f3cf8..232e1063497f 100644
--- a/browser/modules/test/unit/test_SitePermissions.js
+++ b/browser/modules/test/unit/test_SitePermissions.js
@@ -12,7 +12,7 @@ const MIDI_ENABLED = Services.prefs.getBoolPref("dom.webmidi.enabled");
add_task(async function testPermissionsListing() {
let expectedPermissions = ["autoplay-media", "camera", "cookie", "desktop-notification", "focus-tab-by-prompt",
"geo", "image", "install", "microphone", "plugin:flash", "popup", "screen", "shortcuts",
- "persistent-storage"];
+ "persistent-storage", "storage-access"];
if (RESIST_FINGERPRINTING_ENABLED) {
// Canvas permission should be hidden unless privacy.resistFingerprinting
// is true.
@@ -119,7 +119,8 @@ add_task(async function testExactHostMatch() {
exactHostMatched.push("midi");
exactHostMatched.push("midi-sysex");
}
- let nonExactHostMatched = ["image", "cookie", "plugin:flash", "popup", "install", "shortcuts"];
+ let nonExactHostMatched = ["image", "cookie", "plugin:flash", "popup", "install", "shortcuts",
+ "storage-access"];
let permissions = SitePermissions.listPermissions();
for (let permission of permissions) {
diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css
index 8fd1dd9435f9..0aa259d78348 100644
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -32,7 +32,9 @@
}
.popup-notification-icon[popupid="persistent-storage"],
-.persistent-storage-icon {
+.popup-notification-icon[popupid="storage-access"],
+.persistent-storage-icon,
+.storage-access-icon {
list-style-image: url(chrome://browser/skin/notification-icons/persistent-storage.svg);
}
@@ -73,6 +75,24 @@
list-style-image: url(chrome://browser/skin/notification-icons/autoplay-media-blocked.svg);
}
+.storage-access-notification-content {
+ color: var(--panel-disabled-color);
+ font-style: italic;
+ margin-top: 15px;
+}
+
+.storage-access-notification-content .text-link {
+ color: -moz-nativehyperlinktext;
+}
+
+.storage-access-notification-content .text-link:hover {
+ text-decoration: underline;
+}
+
+#storage-access-notification .popup-notification-body-container {
+ padding: 20px;
+}
+
.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
.indexedDB-icon {
list-style-image: url(chrome://browser/skin/notification-icons/indexedDB.svg);
diff --git a/dom/base/StorageAccessPermissionRequest.cpp b/dom/base/StorageAccessPermissionRequest.cpp
new file mode 100644
index 000000000000..40f48cd84b0e
--- /dev/null
+++ b/dom/base/StorageAccessPermissionRequest.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "StorageAccessPermissionRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(StorageAccessPermissionRequest,
+ ContentPermissionRequestBase)
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(StorageAccessPermissionRequest,
+ ContentPermissionRequestBase)
+
+StorageAccessPermissionRequest::StorageAccessPermissionRequest(
+ nsPIDOMWindowInner* aWindow,
+ nsIPrincipal* aNodePrincipal,
+ AllowCallback&& aAllowCallback,
+ AllowAnySiteCallback&& aAllowAnySiteCallback,
+ CancelCallback&& aCancelCallback)
+ : ContentPermissionRequestBase(aNodePrincipal, false, aWindow,
+ NS_LITERAL_CSTRING("dom.storage_access"),
+ NS_LITERAL_CSTRING("storage-access")),
+ mAllowCallback(std::move(aAllowCallback)),
+ mAllowAnySiteCallback(std::move(aAllowAnySiteCallback)),
+ mCancelCallback(std::move(aCancelCallback)),
+ mCallbackCalled(false)
+{
+ mPermissionRequests.AppendElement(PermissionRequest(mType, nsTArray()));
+}
+
+StorageAccessPermissionRequest::~StorageAccessPermissionRequest()
+{
+ Cancel();
+}
+
+NS_IMETHODIMP
+StorageAccessPermissionRequest::Cancel()
+{
+ if (!mCallbackCalled) {
+ mCallbackCalled = true;
+ mCancelCallback();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageAccessPermissionRequest::Allow(JS::HandleValue aChoices)
+{
+ nsTArray choices;
+ nsresult rv = TranslateChoices(aChoices, mPermissionRequests, choices);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!mCallbackCalled) {
+ mCallbackCalled = true;
+ if (choices.Length() == 1 &&
+ choices[0].choice().EqualsLiteral("allow-on-any-site")) {
+ mAllowAnySiteCallback();
+ } else {
+ mAllowCallback();
+ }
+ }
+ return NS_OK;
+}
+
+already_AddRefed
+StorageAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow,
+ AllowCallback&& aAllowCallback,
+ AllowAnySiteCallback&& aAllowAnySiteCallback,
+ CancelCallback&& aCancelCallback)
+{
+ if (!aWindow) {
+ return nullptr;
+ }
+ nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(aWindow);
+ if (!win->GetPrincipal()) {
+ return nullptr;
+ }
+ RefPtr request =
+ new StorageAccessPermissionRequest(aWindow,
+ win->GetPrincipal(),
+ std::move(aAllowCallback),
+ std::move(aAllowAnySiteCallback),
+ std::move(aCancelCallback));
+ return request.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/StorageAccessPermissionRequest.h b/dom/base/StorageAccessPermissionRequest.h
new file mode 100644
index 000000000000..5af43b95b705
--- /dev/null
+++ b/dom/base/StorageAccessPermissionRequest.h
@@ -0,0 +1,58 @@
+/* -*- 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 StorageAccessPermissionRequest_h_
+#define StorageAccessPermissionRequest_h_
+
+#include "nsContentPermissionHelper.h"
+
+#include
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class StorageAccessPermissionRequest final : public ContentPermissionRequestBase
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StorageAccessPermissionRequest,
+ ContentPermissionRequestBase)
+
+ // nsIContentPermissionRequest
+ NS_IMETHOD Cancel(void) override;
+ NS_IMETHOD Allow(JS::HandleValue choices) override;
+
+ typedef std::function AllowCallback;
+ typedef std::function AllowAnySiteCallback;
+ typedef std::function CancelCallback;
+
+ static already_AddRefed Create(
+ nsPIDOMWindowInner* aWindow,
+ AllowCallback&& aAllowCallback,
+ AllowAnySiteCallback&& aAllowAnySiteCallback,
+ CancelCallback&& aCancelCallback);
+
+private:
+ StorageAccessPermissionRequest(nsPIDOMWindowInner* aWindow,
+ nsIPrincipal* aNodePrincipal,
+ AllowCallback&& aAllowCallback,
+ AllowAnySiteCallback&& aAllowAnySiteCallback,
+ CancelCallback&& aCancelCallback);
+ ~StorageAccessPermissionRequest();
+
+ AllowCallback mAllowCallback;
+ AllowAnySiteCallback mAllowAnySiteCallback;
+ CancelCallback mCancelCallback;
+ nsTArray mPermissionRequests;
+ bool mCallbackCalled;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // StorageAccessPermissionRequest_h_
diff --git a/dom/base/moz.build b/dom/base/moz.build
index 0538413ee7ea..0fa4aabf6e5e 100644
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -373,6 +373,7 @@ UNIFIED_SOURCES += [
'Selection.cpp',
'SelectionChangeEventDispatcher.cpp',
'ShadowRoot.cpp',
+ 'StorageAccessPermissionRequest.cpp',
'StructuredCloneBlob.cpp',
'StructuredCloneHolder.cpp',
'StructuredCloneTester.cpp',
diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
index be942cbf7c1d..b01a8b37e2f2 100644
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -284,6 +284,7 @@
#include "NodeUbiReporting.h"
#include "nsICookieService.h"
#include "mozilla/net/RequestContextService.h"
+#include "StorageAccessPermissionRequest.h"
using namespace mozilla;
using namespace mozilla::dom;
@@ -13906,7 +13907,7 @@ nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv)
}
// Step 1. If the document already has been granted access, resolve.
- nsPIDOMWindowInner* inner = GetInnerWindow();
+ nsCOMPtr inner = GetInnerWindow();
RefPtr outer;
if (inner) {
outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
@@ -13976,10 +13977,9 @@ nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv)
return promise.forget();
}
- bool granted = true;
- bool isTrackingWindow = false;
if (StaticPrefs::network_cookie_cookieBehavior() ==
- nsICookieService::BEHAVIOR_REJECT_TRACKER) {
+ nsICookieService::BEHAVIOR_REJECT_TRACKER &&
+ inner) {
// Only do something special for third-party tracking content.
if (nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
// Note: If this has returned true, the top-level document is guaranteed
@@ -13996,33 +13996,68 @@ nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv)
isOnAllowList)),
!isOnAllowList);
- isTrackingWindow = true;
- // TODO: prompt for permission
+ auto performFinalChecks = [inner] () -> RefPtr {
+ RefPtr p =
+ new AntiTrackingCommon::StorageAccessFinalCheckPromise::Private(__func__);
+ RefPtr sapr =
+ StorageAccessPermissionRequest::Create(inner,
+ // Allow
+ [p] { p->Resolve(false, __func__); },
+ // Allow on any site
+ [p] { p->Resolve(true, __func__); },
+ // Block
+ [p] { p->Reject(false, __func__); });
+
+ typedef ContentPermissionRequestBase::PromptResult PromptResult;
+ PromptResult pr = sapr->CheckPromptPrefs();
+ bool onAnySite = false;
+ if (pr == PromptResult::Pending) {
+ // Also check our custom pref for the "Allow on any site" case
+ if (Preferences::GetBool("dom.storage_access.prompt.testing", false) &&
+ Preferences::GetBool("dom.storage_access.prompt.testing.allowonanysite", false)) {
+ pr = PromptResult::Granted;
+ onAnySite = true;
+ }
+ }
+
+ if (pr != PromptResult::Pending) {
+ MOZ_ASSERT_IF(pr != PromptResult::Granted,
+ pr == PromptResult::Denied);
+ if (pr == PromptResult::Granted) {
+ return AntiTrackingCommon::StorageAccessFinalCheckPromise::
+ CreateAndResolve(onAnySite, __func__);
+ }
+ return AntiTrackingCommon::StorageAccessFinalCheckPromise::
+ CreateAndReject(false, __func__);
+ }
+
+ sapr->RequestDelayedTask(inner->EventTargetFor(TaskCategory::Other),
+ ContentPermissionRequestBase::DelayedTaskType::Request);
+ return p.forget();
+ };
+ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
+ NodePrincipal(),
+ inner,
+ AntiTrackingCommon::eStorageAccessAPI,
+ performFinalChecks)->Then(GetCurrentThreadSerialEventTarget(), __func__,
+ [outer, promise] {
+ // Step 10. Grant the document access to cookies and store that fact for
+ // the purposes of future calls to hasStorageAccess() and
+ // requestStorageAccess().
+ outer->SetHasStorageAccess(true);
+ promise->MaybeResolveWithUndefined();
+ },
+ [outer, promise] {
+ outer->SetHasStorageAccess(false);
+ promise->MaybeRejectWithUndefined();
+ });
+
+ return promise.forget();
}
}
- // Step 10. Grant the document access to cookies and store that fact for
- // the purposes of future calls to hasStorageAccess() and
- // requestStorageAccess().
- if (granted && inner) {
- if (isTrackingWindow) {
- AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(NodePrincipal(),
- inner,
- AntiTrackingCommon::eStorageAccessAPI)
- ->Then(GetCurrentThreadSerialEventTarget(), __func__,
- [outer, promise] (bool) {
- outer->SetHasStorageAccess(true);
- promise->MaybeResolveWithUndefined();
- },
- [outer, promise] (bool) {
- outer->SetHasStorageAccess(false);
- promise->MaybeRejectWithUndefined();
- });
- } else {
- outer->SetHasStorageAccess(true);
- promise->MaybeResolveWithUndefined();
- }
- }
+ outer->SetHasStorageAccess(true);
+ promise->MaybeResolveWithUndefined();
return promise.forget();
}
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index ec3bfdf0562c..3b7b944ebebd 100644
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -6010,14 +6010,23 @@ ContentParent::RecvBHRThreadHang(const HangDetails& aDetails)
mozilla::ipc::IPCResult
ContentParent::RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
+ const Principal& aTrackingPrincipal,
const nsCString& aTrackingOrigin,
const nsCString& aGrantedOrigin,
+ const bool& aAnySite,
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver)
{
AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(aParentPrincipal,
+ aTrackingPrincipal,
aTrackingOrigin,
aGrantedOrigin,
- std::move(aResolver));
+ aAnySite)
+ ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+ [aResolver = std::move(aResolver)]
+ (AntiTrackingCommon::FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
+ bool success = aValue.IsResolve() && NS_SUCCEEDED(aValue.ResolveValue());
+ aResolver(success);
+ });
return IPC_OK();
}
diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h
index b60adc65ff05..a143cc2322a9 100644
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1248,8 +1248,10 @@ public:
virtual mozilla::ipc::IPCResult
RecvFirstPartyStorageAccessGrantedForOrigin(const Principal& aParentPrincipal,
+ const Principal& aTrackingPrincipal,
const nsCString& aTrackingOrigin,
const nsCString& aGrantedOrigin,
+ const bool& aAnySite,
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver) override;
virtual mozilla::ipc::IPCResult
diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl
index 14eba1791976..1bff1c94d796 100644
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1157,8 +1157,10 @@ parent:
* granted to have access to aGrantedOrigin when loaded by aParentPrincipal.
*/
async FirstPartyStorageAccessGrantedForOrigin(Principal aParentPrincipal,
+ Principal aTrackingPrincipal,
nsCString aTrackingOrigin,
- nsCString aGrantedOrigin)
+ nsCString aGrantedOrigin,
+ bool aAnySite)
returns (bool unused);
async StoreUserInteractionAsPermission(Principal aPrincipal);
diff --git a/toolkit/components/antitracking/AntiTrackingCommon.cpp b/toolkit/components/antitracking/AntiTrackingCommon.cpp
index b84054d6f1a2..ee29ced04691 100644
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -417,19 +417,20 @@ CompareBaseDomains(nsIURI* aTrackingURI,
/* static */ RefPtr
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal,
nsPIDOMWindowInner* aParentWindow,
- StorageAccessGrantedReason aReason)
+ StorageAccessGrantedReason aReason,
+ const AntiTrackingCommon::PerformFinalChecks& aPerformFinalChecks)
{
MOZ_ASSERT(aParentWindow);
nsCOMPtr uri;
- nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ aPrincipal->GetURI(getter_AddRefs(uri));
if (NS_WARN_IF(!uri)) {
LOG(("Can't get the URI from the principal"));
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
}
nsAutoString origin;
- rv = nsContentUtils::GetUTFOrigin(uri, origin);
+ nsresult rv = nsContentUtils::GetUTFOrigin(uri, origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOG(("Can't get the origin from the URI"));
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
@@ -454,7 +455,7 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa
nsAutoCString trackingOrigin;
nsCOMPtr trackingPrincipal;
- nsGlobalWindowInner* parentWindow = nsGlobalWindowInner::Cast(aParentWindow);
+ RefPtr parentWindow = nsGlobalWindowInner::Cast(aParentWindow);
nsGlobalWindowOuter* outerParentWindow =
nsGlobalWindowOuter::Cast(parentWindow->GetOuterWindow());
if (NS_WARN_IF(!outerParentWindow)) {
@@ -510,7 +511,13 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa
// user-interaction state, because it could be that the current process has
// just sent the request to store the user-interaction permission into the
// parent, without having received the permission itself yet.
- const uint32_t blockReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER;
+ //
+ // We define this as an enum, since without that MSVC fails to capturing this
+ // name inside the lambda without the explicit capture and clang warns if
+ // there is an explicit capture with -Wunused-lambda-capture.
+ enum : uint32_t {
+ blockReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
+ };
if ((aReason != eOpenerAfterUserInteraction ||
nsContentUtils::IsURIInPrefList(trackingURI,
"privacy.restrict3rdpartystorage.userInteractionRequiredForHosts")) &&
@@ -528,65 +535,88 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
}
- NS_ConvertUTF16toUTF8 grantedOrigin(origin);
+ auto storePermission = [pwin, parentWindow, origin, trackingOrigin,
+ trackingPrincipal, trackingURI, topInnerWindow,
+ topLevelStoragePrincipal, aReason]
+ (bool aAnySite) -> RefPtr {
+ NS_ConvertUTF16toUTF8 grantedOrigin(origin);
- nsAutoCString permissionKey;
- CreatePermissionKey(trackingOrigin, grantedOrigin, permissionKey);
+ nsAutoCString permissionKey;
+ CreatePermissionKey(trackingOrigin, grantedOrigin, permissionKey);
- // Let's store the permission in the current parent window.
- topInnerWindow->SaveStorageAccessGranted(permissionKey);
+ // Let's store the permission in the current parent window.
+ topInnerWindow->SaveStorageAccessGranted(permissionKey);
- // Let's inform the parent window.
- parentWindow->StorageAccessGranted();
+ // Let's inform the parent window.
+ parentWindow->StorageAccessGranted();
- nsIChannel* channel =
- pwin->GetCurrentInnerWindow()->GetExtantDoc()->GetChannel();
+ nsIChannel* channel =
+ pwin->GetCurrentInnerWindow()->GetExtantDoc()->GetChannel();
- pwin->NotifyContentBlockingState(blockReason, channel, false, trackingURI);
+ pwin->NotifyContentBlockingState(blockReason, channel, false, trackingURI);
- ReportUnblockingConsole(parentWindow, NS_ConvertUTF8toUTF16(trackingOrigin),
- origin, aReason);
+ ReportUnblockingConsole(parentWindow, NS_ConvertUTF8toUTF16(trackingOrigin),
+ origin, aReason);
- if (XRE_IsParentProcess()) {
- LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
+ if (XRE_IsParentProcess()) {
+ LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
+ trackingOrigin.get(), grantedOrigin.get()));
+
+ return SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
+ trackingPrincipal,
+ trackingOrigin,
+ grantedOrigin,
+ aAnySite)
+ ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+ [] (FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()), __func__);
+ }
+ return StorageAccessGrantPromise::CreateAndReject(false, __func__);
+ });
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+
+ LOG(("Asking the parent process to save the permission for us: trackingOrigin=%s, grantedOrigin=%s",
trackingOrigin.get(), grantedOrigin.get()));
- RefPtr p = new StorageAccessGrantPromise::Private(__func__);
- SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
- trackingOrigin,
- grantedOrigin,
- [p] (bool success) {
- p->Resolve(success, __func__);
- });
- return p;
+ // This is not really secure, because here we have the content process sending
+ // the request of storing a permission.
+ return cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
+ IPC::Principal(trackingPrincipal),
+ trackingOrigin,
+ grantedOrigin,
+ aAnySite)
+ ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+ [] (const ContentChild::FirstPartyStorageAccessGrantedForOriginPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ return StorageAccessGrantPromise::CreateAndResolve(aValue.ResolveValue(), __func__);
+ }
+ return StorageAccessGrantPromise::CreateAndReject(false, __func__);
+ });
+ };
+
+ if (aPerformFinalChecks) {
+ return aPerformFinalChecks()
+ ->Then(GetCurrentThreadSerialEventTarget(), __func__,
+ [storePermission] (StorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ return storePermission(aValue.ResolveValue());
+ }
+ return StorageAccessGrantPromise::CreateAndReject(false, __func__);
+ });
}
-
- ContentChild* cc = ContentChild::GetSingleton();
- MOZ_ASSERT(cc);
-
- LOG(("Asking the parent process to save the permission for us: trackingOrigin=%s, grantedOrigin=%s",
- trackingOrigin.get(), grantedOrigin.get()));
-
- // This is not really secure, because here we have the content process sending
- // the request of storing a permission.
- RefPtr p = new StorageAccessGrantPromise::Private(__func__);
- cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
- trackingOrigin,
- grantedOrigin)
- ->Then(GetCurrentThreadSerialEventTarget(), __func__,
- [p] (bool success) {
- p->Resolve(success, __func__);
- }, [p] (ipc::ResponseRejectReason aReason) {
- p->Reject(false, __func__);
- });
- return p;
+ return storePermission(false);
}
-/* static */ void
+/* static */ RefPtr
AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aParentPrincipal,
+ nsIPrincipal* aTrackingPrincipal,
const nsCString& aTrackingOrigin,
const nsCString& aGrantedOrigin,
- FirstPartyStorageAccessGrantedForOriginResolver&& aResolver)
+ bool aAnySite)
{
MOZ_ASSERT(XRE_IsParentProcess());
@@ -598,15 +628,13 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n
if (NS_WARN_IF(!aParentPrincipal)) {
// The child process is sending something wrong. Let's ignore it.
LOG(("aParentPrincipal is null, bailing out early"));
- aResolver(false);
- return;
+ return FirstPartyStorageAccessGrantPromise::CreateAndReject(false, __func__);
}
nsCOMPtr pm = services::GetPermissionManager();
if (NS_WARN_IF(!pm)) {
LOG(("Permission manager is null, bailing out early"));
- aResolver(false);
- return;
+ return FirstPartyStorageAccessGrantPromise::CreateAndReject(false, __func__);
}
// Remember that this pref is stored in seconds!
@@ -615,28 +643,47 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n
StaticPrefs::privacy_restrict3rdpartystorage_expiration() * 1000;
int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
- uint32_t privateBrowsingId = 0;
- nsresult rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
- if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
- // If we are coming from a private window, make sure to store a session-only
- // permission which won't get persisted to disk.
- expirationType = nsIPermissionManager::EXPIRE_SESSION;
- when = 0;
+ nsresult rv;
+ if (aAnySite) {
+ uint32_t privateBrowsingId = 0;
+ rv = aTrackingPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
+ if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
+ // If we are coming from a private window, make sure to store a session-only
+ // permission which won't get persisted to disk.
+ expirationType = nsIPermissionManager::EXPIRE_SESSION;
+ when = 0;
+ }
+
+ LOG(("Setting 'any site' permission expiry: %u, proceeding to save in the permission manager",
+ expirationTime));
+
+ rv = pm->AddFromPrincipal(aTrackingPrincipal, "cookie",
+ nsICookiePermission::ACCESS_ALLOW,
+ expirationType, when);
+ } else {
+ uint32_t privateBrowsingId = 0;
+ rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
+ if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
+ // If we are coming from a private window, make sure to store a session-only
+ // permission which won't get persisted to disk.
+ expirationType = nsIPermissionManager::EXPIRE_SESSION;
+ when = 0;
+ }
+
+ nsAutoCString type;
+ CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type);
+
+ LOG(("Computed permission key: %s, expiry: %u, proceeding to save in the permission manager",
+ type.get(), expirationTime));
+
+ rv = pm->AddFromPrincipal(aParentPrincipal, type.get(),
+ nsIPermissionManager::ALLOW_ACTION,
+ expirationType, when);
}
-
- nsAutoCString type;
- CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type);
-
- LOG(("Computed permission key: %s, expiry: %u, proceeding to save in the permission manager",
- type.get(), expirationTime));
-
- rv = pm->AddFromPrincipal(aParentPrincipal, type.get(),
- nsIPermissionManager::ALLOW_ACTION,
- expirationType, when);
Unused << NS_WARN_IF(NS_FAILED(rv));
- aResolver(NS_SUCCEEDED(rv));
LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure"));
+ return FirstPartyStorageAccessGrantPromise::CreateAndResolve(rv, __func__);
}
// static
diff --git a/toolkit/components/antitracking/AntiTrackingCommon.h b/toolkit/components/antitracking/AntiTrackingCommon.h
index 46dce0bd4d17..6e71e10b4e0b 100644
--- a/toolkit/components/antitracking/AntiTrackingCommon.h
+++ b/toolkit/components/antitracking/AntiTrackingCommon.h
@@ -93,11 +93,14 @@ public:
// Ex: example.net import tracker.com/script.js which does opens a popup and
// the user interacts with it. tracker.com is allowed when loaded by
// example.net.
- typedef MozPromise StorageAccessGrantPromise;
+ typedef MozPromise StorageAccessFinalCheckPromise;
+ typedef std::function()> PerformFinalChecks;
+ typedef MozPromise StorageAccessGrantPromise;
static MOZ_MUST_USE RefPtr
AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal,
nsPIDOMWindowInner* aParentWindow,
- StorageAccessGrantedReason aReason);
+ StorageAccessGrantedReason aReason,
+ const PerformFinalChecks& aPerformFinalChecks = nullptr);
// Returns true if the permission passed in is a storage access permission
// for the passed in principal argument.
@@ -111,11 +114,13 @@ public:
HasUserInteraction(nsIPrincipal* aPrincipal);
// For IPC only.
- static void
+ typedef MozPromise FirstPartyStorageAccessGrantPromise;
+ static RefPtr
SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aTrackingPrinciapl,
const nsCString& aParentOrigin,
const nsCString& aGrantedOrigin,
- FirstPartyStorageAccessGrantedForOriginResolver&& aResolver);
+ bool aAnySite);
enum ContentBlockingAllowListPurpose {
eStorageChecks,
diff --git a/toolkit/components/antitracking/test/browser/browser.ini b/toolkit/components/antitracking/test/browser/browser.ini
index fb8cd3ceac74..6e70bd4d50ff 100644
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -1,4 +1,9 @@
[DEFAULT]
+prefs =
+ # Disable the Storage Access API prompts for all of the tests in this directory
+ dom.storage_access.prompt.testing=true
+ dom.storage_access.prompt.testing.allow=true
+
support-files =
embedder.html
head.js
diff --git a/toolkit/content/widgets/notification.xml b/toolkit/content/widgets/notification.xml
index 337e15104f07..0cadb8a92f47 100644
--- a/toolkit/content/widgets/notification.xml
+++ b/toolkit/content/widgets/notification.xml
@@ -35,7 +35,8 @@
localization file, if necessary). -->
+ xbl:inherits="xbl:text=endlabel,popupid"/>