зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
508e50ac02
Коммит
cc714b7adc
|
@ -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,7 +12,9 @@ var gPermPrincipal;
|
|||
var gUsageRequest;
|
||||
|
||||
// Array of permissionIDs sorted alphabetically by label.
|
||||
var gPermissions = SitePermissions.listPermissions().sort((a, b) => {
|
||||
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);
|
||||
|
|
|
@ -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 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!
|
||||
|
|
|
@ -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().
|
||||
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, promise] {
|
||||
outer->SetHasStorageAccess(false);
|
||||
promise->MaybeRejectWithUndefined();
|
||||
});
|
||||
} else {
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +535,10 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa
|
|||
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
||||
}
|
||||
|
||||
auto storePermission = [pwin, parentWindow, origin, trackingOrigin,
|
||||
trackingPrincipal, trackingURI, topInnerWindow,
|
||||
topLevelStoragePrincipal, aReason]
|
||||
(bool aAnySite) -> RefPtr<StorageAccessGrantPromise> {
|
||||
NS_ConvertUTF16toUTF8 grantedOrigin(origin);
|
||||
|
||||
nsAutoCString permissionKey;
|
||||
|
@ -551,14 +562,18 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa
|
|||
LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
|
||||
trackingOrigin.get(), grantedOrigin.get()));
|
||||
|
||||
RefPtr<StorageAccessGrantPromise::Private> p = new StorageAccessGrantPromise::Private(__func__);
|
||||
SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
|
||||
return SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(topLevelStoragePrincipal,
|
||||
trackingPrincipal,
|
||||
trackingOrigin,
|
||||
grantedOrigin,
|
||||
[p] (bool success) {
|
||||
p->Resolve(success, __func__);
|
||||
aAnySite)
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
[] (FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
|
||||
if (aValue.IsResolve()) {
|
||||
return StorageAccessGrantPromise::CreateAndResolve(NS_SUCCEEDED(aValue.ResolveValue()), __func__);
|
||||
}
|
||||
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
||||
});
|
||||
return p;
|
||||
}
|
||||
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
|
@ -569,24 +584,39 @@ AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(nsIPrincipal* aPrincipa
|
|||
|
||||
// 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),
|
||||
return cc->SendFirstPartyStorageAccessGrantedForOrigin(IPC::Principal(topLevelStoragePrincipal),
|
||||
IPC::Principal(trackingPrincipal),
|
||||
trackingOrigin,
|
||||
grantedOrigin)
|
||||
grantedOrigin,
|
||||
aAnySite)
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
[p] (bool success) {
|
||||
p->Resolve(success, __func__);
|
||||
}, [p] (ipc::ResponseRejectReason aReason) {
|
||||
p->Reject(false, __func__);
|
||||
[] (const ContentChild::FirstPartyStorageAccessGrantedForOriginPromise::ResolveOrRejectValue& aValue) {
|
||||
if (aValue.IsResolve()) {
|
||||
return StorageAccessGrantPromise::CreateAndResolve(aValue.ResolveValue(), __func__);
|
||||
}
|
||||
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
||||
});
|
||||
return p;
|
||||
};
|
||||
|
||||
if (aPerformFinalChecks) {
|
||||
return aPerformFinalChecks()
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
[storePermission] (StorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
|
||||
if (aValue.IsResolve()) {
|
||||
return storePermission(aValue.ResolveValue());
|
||||
}
|
||||
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
||||
});
|
||||
}
|
||||
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,8 +643,26 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n
|
|||
StaticPrefs::privacy_restrict3rdpartystorage_expiration() * 1000;
|
||||
int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
|
||||
|
||||
nsresult rv;
|
||||
if (aAnySite) {
|
||||
uint32_t privateBrowsingId = 0;
|
||||
nsresult rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
|
||||
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.
|
||||
|
@ -633,10 +679,11 @@ AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(n
|
|||
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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче