Bug 1490811 - Part 1: Add a permission doorhanger for the storage access API r=baku,johannh

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ehsan Akhgari 2018-11-26 21:23:16 +00:00
Родитель 508e50ac02
Коммит cc714b7adc
24 изменённых файлов: 600 добавлений и 126 удалений

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

@ -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);

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

@ -977,6 +977,8 @@ xmlns="http://www.w3.org/1999/xhtml"
tooltiptext="&urlbar.midiNotificationAnchor.tooltip;"/>
<image id="webauthn-notification-icon" class="notification-anchor-icon" role="button"
tooltiptext="&urlbar.webAuthnAnchor.tooltip;"/>
<image id="storage-access-notification-icon" class="notification-anchor-icon storage-access-icon" role="button"
tooltiptext="&urlbar.storageAccessAnchor.tooltip;"/>
</box>
<image id="connection-icon"/>
<image id="extension-icon"/>

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

@ -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) {

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

@ -103,3 +103,18 @@
</hbox>
</popupnotificationfooter>
</popupnotification>
<popupnotification id="storage-access-notification" hidden="true">
<popupnotificationcontent class="storage-access-notification-content">
<xul:vbox flex="1">
<!-- These need to be on the same line to avoid creating
whitespace between them (whitespace is added in the
localization file, if necessary). -->
<xul:description class="storage-access-perm-text"><html:span
id="storage-access-perm-label"/><html:a id="storage-access-perm-learnmore"
onclick="openTrustedLinkIn(this.href, 'tab'); return false;"
class="text-link popup-notification-learnmore-link"/><html:span
id="storage-access-perm-endlabel"/></xul:description>
</xul:vbox>
</popupnotificationcontent>
</popupnotification>

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

@ -3135,6 +3135,9 @@ const ContentPermissionIntegration = {
case "autoplay-media": {
return new PermissionUI.AutoplayPermissionPrompt(request);
}
case "storage-access": {
return new PermissionUI.StorageAccessPermissionPrompt(request);
}
}
return undefined;
},

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

@ -282,6 +282,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY urlbar.persistentStorageNotificationAnchor.tooltip "Store data in Persistent Storage">
<!ENTITY urlbar.remoteControlNotificationAnchor.tooltip "Browser is under remote control">
<!ENTITY urlbar.webAuthnAnchor.tooltip "Open Web Authentication panel">
<!ENTITY urlbar.storageAccessAnchor.tooltip "Open browsing activity permission panel">
<!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.tooltip "Manage sharing your camera and/or microphone with the site">
<!ENTITY urlbar.webRTCShareMicrophoneNotificationAnchor.tooltip "Manage sharing your microphone with the site">

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

@ -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 dont 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!

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

@ -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
permission.midi-sysex.label = Access MIDI Devices with SysEx Support

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

@ -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;

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

@ -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")) {

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

@ -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) {

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

@ -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);

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

@ -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<nsString>()));
}
StorageAccessPermissionRequest::~StorageAccessPermissionRequest()
{
Cancel();
}
NS_IMETHODIMP
StorageAccessPermissionRequest::Cancel()
{
if (!mCallbackCalled) {
mCallbackCalled = true;
mCancelCallback();
}
return NS_OK;
}
NS_IMETHODIMP
StorageAccessPermissionRequest::Allow(JS::HandleValue aChoices)
{
nsTArray<PermissionChoice> 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>
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<StorageAccessPermissionRequest> request =
new StorageAccessPermissionRequest(aWindow,
win->GetPrincipal(),
std::move(aAllowCallback),
std::move(aAllowAnySiteCallback),
std::move(aCancelCallback));
return request.forget();
}
} // namespace dom
} // namespace mozilla

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

@ -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 <functional>
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<void()> AllowCallback;
typedef std::function<void()> AllowAnySiteCallback;
typedef std::function<void()> CancelCallback;
static already_AddRefed<StorageAccessPermissionRequest> 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<PermissionRequest> mPermissionRequests;
bool mCallbackCalled;
};
} // namespace dom
} // namespace mozilla
#endif // StorageAccessPermissionRequest_h_

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

@ -373,6 +373,7 @@ UNIFIED_SOURCES += [
'Selection.cpp',
'SelectionChangeEventDispatcher.cpp',
'ShadowRoot.cpp',
'StorageAccessPermissionRequest.cpp',
'StructuredCloneBlob.cpp',
'StructuredCloneHolder.cpp',
'StructuredCloneTester.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<nsPIDOMWindowInner> inner = GetInnerWindow();
RefPtr<nsGlobalWindowOuter> 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<AntiTrackingCommon::StorageAccessFinalCheckPromise> {
RefPtr<AntiTrackingCommon::StorageAccessFinalCheckPromise::Private> p =
new AntiTrackingCommon::StorageAccessFinalCheckPromise::Private(__func__);
RefPtr<StorageAccessPermissionRequest> 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();
}

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

@ -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();
}

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

@ -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

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

@ -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);

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

@ -417,19 +417,20 @@ CompareBaseDomains(nsIURI* aTrackingURI,
/* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipal,
nsPIDOMWindowInner* aParentWindow,
StorageAccessGrantedReason aReason)
StorageAccessGrantedReason aReason,
const AntiTrackingCommon::PerformFinalChecks& aPerformFinalChecks)
{
MOZ_ASSERT(aParentWindow);
nsCOMPtr<nsIURI> 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<nsIPrincipal> trackingPrincipal;
nsGlobalWindowInner* parentWindow = nsGlobalWindowInner::Cast(aParentWindow);
RefPtr<nsGlobalWindowInner> 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<StorageAccessGrantPromise> {
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<StorageAccessGrantPromise::Private> 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<StorageAccessGrantPromise::Private> 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<mozilla::AntiTrackingCommon::FirstPartyStorageAccessGrantPromise>
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<nsIPermissionManager> 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

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

@ -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<bool, bool, false> StorageAccessGrantPromise;
typedef MozPromise<bool, bool, true> StorageAccessFinalCheckPromise;
typedef std::function<RefPtr<StorageAccessFinalCheckPromise>()> PerformFinalChecks;
typedef MozPromise<bool, bool, true> StorageAccessGrantPromise;
static MOZ_MUST_USE RefPtr<StorageAccessGrantPromise>
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<nsresult, bool, true> FirstPartyStorageAccessGrantPromise;
static RefPtr<FirstPartyStorageAccessGrantPromise>
SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(nsIPrincipal* aPrincipal,
nsIPrincipal* aTrackingPrinciapl,
const nsCString& aParentOrigin,
const nsCString& aGrantedOrigin,
FirstPartyStorageAccessGrantedForOriginResolver&& aResolver);
bool aAnySite);
enum ContentBlockingAllowListPurpose {
eStorageChecks,

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

@ -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

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

@ -35,7 +35,8 @@
localization file, if necessary). -->
<xul:description class="popup-notification-description" xbl:inherits="popupid"><html:span
xbl:inherits="xbl:text=label,popupid"/><html:b xbl:inherits="xbl:text=name,popupid"/><html:span
xbl:inherits="xbl:text=endlabel,popupid"/></xul:description>
xbl:inherits="xbl:text=endlabel,popupid"/><html:b xbl:inherits="xbl:text=secondname,popupid"/><html:span
xbl:inherits="xbl:text=secondendlabel,popupid"/></xul:description>
</xul:vbox>
<xul:toolbarbutton anonid="closebutton"
class="messageCloseButton close-icon popup-notification-closebutton tabbable"

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

@ -329,10 +329,12 @@ PopupNotifications.prototype = {
* at a time. If a notification already exists with the given ID, it
* will be replaced.
* @param message
* A string containing the text to be displayed as the notification header.
* The string may optionally contain "<>" as a placeholder which is later
* replaced by a host name or an addon name that is formatted to look bold,
* in which case the options.name property needs to be specified.
* A string containing the text to be displayed as the notification
* header. The string may optionally contain one or two "<>" as a
* placeholder which is later replaced by a host name or an addon name
* that is formatted to look bold, in which case the options.name
* property (as well as options.secondName if passing two "<>"
* placeholders) needs to be specified.
* @param anchorID
* The ID of the element that should be used as this notification
* popup's anchor. May be null, in which case the notification will be
@ -460,6 +462,11 @@ PopupNotifications.prototype = {
* An optional string formatted to look bold and used in the
* notifiation description header text. Usually a host name or
* addon name.
* secondName:
* An optional string formatted to look bold and used in the
* notification description header text. Usually a host name or
* addon name. This is similar to name, and only used in case
* where message contains two "<>" placeholders.
* @returns the Notification object corresponding to the added notification.
*/
show: function PopupNotifications_show(browser, id, message, anchorID,
@ -781,6 +788,13 @@ PopupNotifications.prototype = {
text.start = array[0] || "";
text.name = n.options.name || "";
text.end = array[1] || "";
if (array.length == 3) {
text.secondName = n.options.secondName || "";
text.secondEnd = array[2] || "";
} else if (array.length > 3) {
Cu.reportError("Unexpected array length encountered in " +
"_formatDescriptionMessage: " + array.length);
}
return text;
},
@ -807,6 +821,11 @@ PopupNotifications.prototype = {
popupnotification.setAttribute("label", desc.start);
popupnotification.setAttribute("name", desc.name);
popupnotification.setAttribute("endlabel", desc.end);
if (("secondName" in desc) &&
("secondEnd" in desc)) {
popupnotification.setAttribute("secondname", desc.secondName);
popupnotification.setAttribute("secondendlabel", desc.secondEnd);
}
popupnotification.setAttribute("id", popupnotificationID);
popupnotification.setAttribute("popupid", n.id);