--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6012,18 +6012,22 @@ var OfflineApps = {
let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
// This message shows the quota in MB, and so we divide the quota (in kb) by 1024.
let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
[ uri.host,
warnQuotaKB / 1024 ]);
let anchorID = "indexedDB-notification-icon";
+ let options = {
+ persistent: true,
+ hideClose: true
+ };
PopupNotifications.show(browser, "offline-app-usage", message,
- anchorID, mainAction, null, { persistent: true });
+ anchorID, mainAction, null, options);
// Now that we've warned once, prevent the warning from showing up
// again.
Services.perms.add(uri, "offline-app",
Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
},
// XXX: duplicated in preferences/advanced.js
@@ -6072,38 +6076,39 @@ var OfflineApps = {
let notification = PopupNotifications.getNotification(notificationID, browser);
if (notification) {
notification.options.controlledItems.push([
Cu.getWeakReference(browser), docId, uri
]);
} else {
let mainAction = {
- label: gNavigatorBundle.getString("offlineApps.allow"),
- accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ label: gNavigatorBundle.getString("offlineApps.allowStoring"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowStoring.accesskey"),
callback: function() {
for (let [browser, docId, uri] of notification.options.controlledItems) {
OfflineApps.allowSite(browser, docId, uri);
}
}
};
let secondaryActions = [{
- label: gNavigatorBundle.getString("offlineApps.never"),
- accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ label: gNavigatorBundle.getString("offlineApps.dontAllow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.dontAllow.accesskey"),
callback: function() {
for (let [, , uri] of notification.options.controlledItems) {
OfflineApps.disallowSite(uri);
}
}
}];
- let message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ let message = gNavigatorBundle.getFormattedString("offlineApps.available2",
[host]);
let anchorID = "indexedDB-notification-icon";
let options = {
persistent: true,
+ hideClose: true,
controlledItems : [[Cu.getWeakReference(browser), docId, uri]]
};
notification = PopupNotifications.show(browser, notificationID, message,
anchorID, mainAction,
secondaryActions, options);
}
},
@@ -6175,51 +6180,55 @@ var IndexedDBPromptHelper = {
var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
var browser = requestor.getInterface(Ci.nsIDOMNode);
if (browser.ownerGlobal != window) {
// Only listen for notifications for browsers in our chrome window.
return;
}
- var host = browser.currentURI.asciiHost;
+ // Get the host name if available or the file path otherwise.
+ var host = browser.currentURI.asciiHost || browser.currentURI.path;
var message;
var responseTopic;
if (topic == this._permissionsPrompt) {
- message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ message = gNavigatorBundle.getFormattedString("offlineApps.available2",
[ host ]);
responseTopic = this._permissionsResponse;
}
var observer = requestor.getInterface(Ci.nsIObserver);
var mainAction = {
- label: gNavigatorBundle.getString("offlineApps.allow"),
- accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ label: gNavigatorBundle.getString("offlineApps.allowStoring"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowStoring.accesskey"),
callback: function() {
observer.observe(null, responseTopic,
Ci.nsIPermissionManager.ALLOW_ACTION);
}
};
var secondaryActions = [
{
- label: gNavigatorBundle.getString("offlineApps.never"),
- accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ label: gNavigatorBundle.getString("offlineApps.dontAllow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.dontAllow.accesskey"),
callback: function() {
observer.observe(null, responseTopic,
Ci.nsIPermissionManager.DENY_ACTION);
}
}
];
PopupNotifications.show(browser, topic, message,
this._notificationIcon, mainAction,
- secondaryActions, { persistent: true });
+ secondaryActions, {
+ persistent: true,
+ hideClose: true,
+ });
}
};
function CanCloseWindow()
{
// Avoid redundant calls to canClose from showing multiple
// PermitUnload dialogs.
if (Services.startup.shuttingDown || window.skipNextCanClose) {
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
@@ -182,32 +182,16 @@ var tests = [
let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing");
showNotification(notifyObj);
yield promiseTopic;
isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor should be visible");
goNext();
}
},
- // Test notification "Not Now" menu item
- { id: "Test#8",
- run: function () {
- this.notifyObj = new BasicNotification(this.id);
- this.notification = showNotification(this.notifyObj);
- },
- onShown: function (popup) {
- checkPopup(popup, this.notifyObj);
- triggerSecondaryCommand(popup, 1);
- },
- onHidden: function (popup) {
- ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
- this.notification.remove();
- ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
- }
- },
// Test notification close button
{ id: "Test#9",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
@@ -158,33 +158,16 @@ var tests = [
"The browser should be active");
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
yield BrowserTestUtils.waitForCondition(() => fm.activeWindow == originalWindow,
"The window should be active")
goNext();
}
},
- // the hideNotNow option
- { id: "Test#7",
- run: function () {
- this.notifyObj = new BasicNotification(this.id);
- this.notifyObj.options.hideNotNow = true;
- this.notifyObj.mainAction.dismiss = true;
- this.notification = showNotification(this.notifyObj);
- },
- onShown: function (popup) {
- // checkPopup verifies that the Not Now item is hidden, and that no separator is added.
- checkPopup(popup, this.notifyObj);
- triggerMainCommand(popup);
- },
- onHidden: function (popup) {
- this.notification.remove();
- }
- },
// the main action callback can keep the notification.
{ id: "Test#8",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.mainAction.dismiss = true;
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
@@ -126,19 +126,19 @@ var tests = [
onShown: function* (popup) {
this.complete = false;
// Notification should persist after attempt to dismiss by clicking on the
// content area.
let browser = gBrowser.selectedBrowser;
yield BrowserTestUtils.synthesizeMouseAtCenter("body", {}, browser)
- // Notification should be hidden after dismissal via Not Now.
+ // Notification should be hidden after dismissal via Don't Allow.
this.complete = true;
- triggerSecondaryCommand(popup, 1);
+ triggerSecondaryCommand(popup, 0);
},
onHidden: function (popup) {
ok(this.complete, "Should have hidden the notification after clicking Not Now");
this.notification.remove();
gBrowser.removeTab(gBrowser.selectedTab);
gBrowser.selectedTab = this.oldSelectedTab;
}
},
--- a/browser/base/content/test/popupNotifications/head.js
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -208,35 +208,38 @@ function checkPopup(popup, notifyObj) {
is(notification.getAttribute("label"), notifyObj.message, "message matches");
is(notification.id, notifyObj.id + "-notification", "id matches");
if (notifyObj.mainAction) {
is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
"main action label matches");
is(notification.getAttribute("buttonaccesskey"),
notifyObj.mainAction.accessKey, "main action accesskey matches");
}
- let actualSecondaryActions =
+ if (notifyObj.secondaryActions && notifyObj.secondaryActions.length == 1) {
+ let secondaryAction = notifyObj.secondaryActions[0];
+ is(notification.getAttribute("secondarybuttonlabel"), secondaryAction.label,
+ "secondary action label matches");
+ is(notification.getAttribute("secondarybuttonaccesskey"),
+ secondaryAction.accessKey, "secondary action accesskey matches");
+ }
+ // Additional secondary actions appear as menu items.
+ let actualExtraSecondaryActions =
Array.filter(notification.childNodes, child => child.nodeName == "menuitem");
- let secondaryActions = notifyObj.secondaryActions || [];
- let actualSecondaryActionsCount = actualSecondaryActions.length;
- if (notifyObj.options.hideNotNow) {
- is(notification.getAttribute("hidenotnow"), "true", "'Not Now' item hidden");
- if (secondaryActions.length)
- is(notification.lastChild.tagName, "menuitem", "no menuseparator");
- }
- else if (secondaryActions.length) {
+ let extraSecondaryActions = notifyObj.secondaryActions ? notifyObj.secondaryActions.slice(1) : [];
+ let actualExtraSecondaryActionsCount = actualExtraSecondaryActions.length;
+ if (extraSecondaryActions.length) {
is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
}
- is(actualSecondaryActionsCount, secondaryActions.length,
- actualSecondaryActions.length + " secondary actions");
- secondaryActions.forEach(function (a, i) {
- is(actualSecondaryActions[i].getAttribute("label"), a.label,
- "label for secondary action " + i + " matches");
- is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey,
- "accessKey for secondary action " + i + " matches");
+ is(actualExtraSecondaryActionsCount, extraSecondaryActions.length,
+ actualExtraSecondaryActions.length + " extra secondary actions");
+ extraSecondaryActions.forEach(function (a, i) {
+ is(actualExtraSecondaryActions[i].getAttribute("label"), a.label,
+ "label for extra secondary action " + i + " matches");
+ is(actualExtraSecondaryActions[i].getAttribute("accesskey"), a.accessKey,
+ "accessKey for extra secondary action " + i + " matches");
});
}
XPCOMUtils.defineLazyGetter(this, "gActiveListeners", () => {
let listeners = new Map();
registerCleanupFunction(() => {
for (let [listener, eventName] of listeners) {
PopupNotifications.panel.removeEventListener(eventName, listener, false);
@@ -267,23 +270,30 @@ function triggerMainCommand(popup) {
}
function triggerSecondaryCommand(popup, index) {
let notifications = popup.childNodes;
ok(notifications.length > 0, "at least one notification displayed");
let notification = notifications[0];
info("Triggering secondary command for notification " + notification.id);
+ if (index == 0) {
+ EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+ return;
+ }
+
+ // Extra secondary actions appear in a menu.
notification.button.nextSibling.nextSibling.focus();
popup.addEventListener("popupshown", function handle() {
popup.removeEventListener("popupshown", handle, false);
info("Command popup open for notification " + notification.id);
- // Press down until the desired command is selected
- for (let i = 0; i <= index; i++) {
+ // Press down until the desired command is selected. Decrease index by one
+ // since the secondary action was handled above.
+ for (let i = 0; i <= index - 1; i++) {
EventUtils.synthesizeKey("VK_DOWN", {});
}
// Activate
EventUtils.synthesizeKey("VK_RETURN", {});
}, false);
// One down event to open the popup
info("Open the popup to trigger secondary command for notification " + notification.id);
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -466,17 +466,17 @@ var gTests = [
gIdentityHandler._identityPopup.hidden = true;
yield expectNoObserverCalled();
yield closeStream();
}
},
{
- desc: "'Always Allow' ignored and not shown on http pages",
+ desc: "'Always Allow' disabled on http pages",
run: function* checkNoAlwaysOnHttp() {
// Load an http page instead of the https version.
let browser = gBrowser.selectedBrowser;
browser.loadURI(browser.documentURI.spec.replace("https://", "http://"));
yield BrowserTestUtils.browserLoaded(browser);
// Initially set both permissions to 'allow'.
let Perms = Services.perms;
@@ -486,26 +486,25 @@ var gTests = [
// Request devices and expect a prompt despite the saved 'Allow' permission,
// because the connection isn't secure.
let promise = promisePopupNotificationShown("webRTC-shareDevices");
yield promiseRequestDevice(true, true);
yield promise;
yield expectObserverCalled("getUserMedia:request");
- // Ensure that the 'Always Allow' action isn't shown.
- let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label");
- ok(!!alwaysLabel, "found the 'Always Allow' localized label");
- let labels = [];
+ // Ensure that checking the 'Remember this decision' checkbox disables
+ // 'Allow'.
let notification = PopupNotifications.panel.firstChild;
- for (let node of notification.childNodes) {
- if (node.localName == "menuitem")
- labels.push(node.getAttribute("label"));
- }
- is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown");
+ let checkbox = notification.checkbox;
+ ok(!!checkbox, "checkbox is present");
+ ok(!checkbox.checked, "checkbox is not checked");
+ checkbox.click();
+ ok(checkbox.checked, "checkbox now checked");
+ ok(notification.button.disabled, "Allow button is disabled");
// Cleanup.
yield closeStream(true);
Perms.remove(uri, "camera");
Perms.remove(uri, "microphone");
}
}
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -300,32 +300,27 @@ function promiseNoPopupNotification(aNam
}
const kActionAlways = 1;
const kActionDeny = 2;
const kActionNever = 3;
function activateSecondaryAction(aAction) {
let notification = PopupNotifications.panel.firstChild;
- notification.button.nextSibling.nextSibling.focus();
- let popup = notification.menupopup;
- popup.addEventListener("popupshown", function () {
- popup.removeEventListener("popupshown", arguments.callee, false);
-
- // Press 'down' as many time as needed to select the requested action.
- while (aAction--)
- EventUtils.synthesizeKey("VK_DOWN", {});
-
- // Activate
- EventUtils.synthesizeKey("VK_RETURN", {});
- }, false);
-
- // One down event to open the popup
- EventUtils.synthesizeKey("VK_DOWN",
- { altKey: !navigator.platform.includes("Mac") });
+ switch (aAction) {
+ case kActionNever:
+ notification.checkbox.setAttribute("checked", true); // fallthrough
+ case kActionDeny:
+ notification.secondaryButton.click();
+ break;
+ case kActionAlways:
+ notification.checkbox.setAttribute("checked", true);
+ notification.button.click();
+ break;
+ }
}
function getMediaCaptureState() {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
resolve(data);
});
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2865,17 +2865,16 @@ var E10SAccessibilityCheck = {
accessKey: win.gNavigatorBundle.getString("e10s.accessibilityNotice.enableAndRestart.accesskey"),
callback: restartCallback,
}];
let options = {
popupIconURL: "chrome://browser/skin/e10s-64@2x.png",
learnMoreURL: Services.urlFormatter.formatURLPref("app.support.e10sAccessibilityUrl"),
persistent: true,
persistWhileVisible: true,
- hideNotNow: true,
};
notification =
win.PopupNotifications.show(browser, "a11y_enabled_with_e10s",
promptMessage, null, mainAction,
secondaryActions, options);
},
};
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
@@ -19,19 +19,19 @@ add_task(function* test() {
// Wait until the notification is available.
while (!notification){
yield new Promise(resolve => { executeSoon(resolve); });
let notification = aWindow.PopupNotifications.getNotification("geolocation");
}
if (aPrivateMode) {
// Make sure the notification is correctly displayed without a remember control
- is(notification.secondaryActions.length, 0, "Secondary actions shouldn't exist (always/never remember)");
+ ok(!notification.options.checkbox.show, "Secondary actions should exist (always/never remember)");
} else {
- ok(notification.secondaryActions.length > 1, "Secondary actions should exist (always/never remember)");
+ ok(notification.options.checkbox.show, "Secondary actions should exist (always/never remember)");
}
notification.remove();
aWindow.gBrowser.removeCurrentTab();
});
};
let win = yield BrowserTestUtils.openNewBrowserWindow();
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -310,23 +310,21 @@ printButton.tooltip=Print this page… (%S)
newWindowButton.tooltip=Open a new window (%S)
# New Tab button tooltip
# LOCALIZATION NOTE (newTabButton.tooltip):
# %S is the keyboard shortcut for "New Tab"
newTabButton.tooltip=Open a new tab (%S)
# Offline web applications
-offlineApps.available=This website (%S) is asking to store data on your computer for offline use.
-offlineApps.allow=Allow
-offlineApps.allowAccessKey=A
-offlineApps.never=Never for This Site
-offlineApps.neverAccessKey=e
-offlineApps.notNow=Not Now
-offlineApps.notNowAccessKey=N
+offlineApps.available2=Will you allow %S to store data on your computer?
+offlineApps.allowStoring=Allow Storing Data
+offlineApps.allowStoring.accesskey=A
+offlineApps.dontAllow=Don’t Allow
+offlineApps.dontAllow.accesskey=n
offlineApps.usage=This website (%S) is now storing more than %SMB of data on your computer for offline use.
offlineApps.manageUsage=Show settings
offlineApps.manageUsageAccessKey=S
identity.identified.verifier=Verified by: %S
identity.identified.verified_by_you=You have added a security exception for this site.
identity.identified.state_and_country=%S, %S
@@ -363,34 +361,31 @@ pu.notifyButton.label=Details…
pu.notifyButton.accesskey=D
# LOCALIZATION NOTE %S will be replaced by the short name of the application.
puNotifyText=%S has been updated
puAlertTitle=%S Updated
puAlertText=Click here for details
# Geolocation UI
-# LOCALIZATION NOTE (geolocation.shareLocation geolocation.alwaysShareLocation geolocation.neverShareLocation):
-# If you're having trouble with the word Share, please use Allow and Block in your language.
-geolocation.shareLocation=Share Location
-geolocation.shareLocation.accesskey=a
-geolocation.alwaysShareLocation=Always Share Location
-geolocation.alwaysShareLocation.accesskey=A
-geolocation.neverShareLocation=Never Share Location
-geolocation.neverShareLocation.accesskey=N
-geolocation.shareWithSite2=Would you like to share your location with this site?
-geolocation.shareWithFile2=Would you like to share your location with this file?
+geolocation.allowLocation=Allow Location Access
+geolocation.allowLocation.accesskey=A
+geolocation.dontAllowLocation=Don’t Allow
+geolocation.dontAllowLocation.accesskey=n
+geolocation.shareWithSite3=Will you allow %S to access your location?
+geolocation.shareWithFile3=Will you allow this local file to access your location?
+geolocation.remember=Remember this decision
-webNotifications.receiveForSession=Receive for this session
-webNotifications.receiveForSession.accesskey=s
-webNotifications.alwaysReceive=Always Receive Notifications
-webNotifications.alwaysReceive.accesskey=A
-webNotifications.neverShow=Always Block Notifications
-webNotifications.neverShow.accesskey=N
-webNotifications.receiveFromSite=Would you like to receive notifications from this site?
+webNotifications.remember=Remember this decision
+webNotifications.rememberForSession=Remember decision for this session
+webNotifications.allow=Allow Notifications
+webNotifications.allow.accesskey=A
+webNotifications.dontAllow=Don’t Allow
+webNotifications.dontAllow.accesskey=n
+webNotifications.receiveFromSite2=Will you allow %S to send notifications?
# LOCALIZATION NOTE (webNotifications.upgradeTitle): When using native notifications on OS X, the title may be truncated around 32 characters.
webNotifications.upgradeTitle=Upgraded notifications
# LOCALIZATION NOTE (webNotifications.upgradeBody): When using native notifications on OS X, the body may be truncated around 100 characters in some views.
webNotifications.upgradeBody=You can now receive notifications from sites that are not currently loaded. Click to learn more.
# Phishing/Malware Notification Bar.
# LOCALIZATION NOTE (notADeceptiveSite, notAnAttack)
# The two button strings will never be shown at the same time, so
@@ -468,29 +463,29 @@ social.error.message=%1$S is unable to c
social.error.tryAgain.label=Try Again
social.error.tryAgain.accesskey=T
social.error.closeSidebar.label=Close This Sidebar
social.error.closeSidebar.accesskey=C
# LOCALIZATION NOTE: %1$S is the label for the toolbar button, %2$S is the associated badge numbering that the social provider may provide.
social.aria.toolbarButtonBadgeText=%1$S (%2$S)
-# LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message,
-# getUserMedia.shareScreen.message, getUserMedia.shareCameraAndMicrophone.message,
-# getUserMedia.shareScreenAndMicrophone.message, getUserMedia.shareCameraAndAudioCapture.message,
-# getUserMedia.shareAudioCapture.message, getUserMedia.shareScreenAndAudioCapture.message):
+# LOCALIZATION NOTE (getUserMedia.shareCamera2.message, getUserMedia.shareMicrophone2.message,
+# getUserMedia.shareScreen2.message, getUserMedia.shareCameraAndMicrophone2.message,
+# getUserMedia.shareScreenAndMicrophone2.message, getUserMedia.shareCameraAndAudioCapture2.message,
+# getUserMedia.shareAudioCapture2.message, getUserMedia.shareScreenAndAudioCapture2.message):
# %S is the website origin (e.g. www.mozilla.org)
-getUserMedia.shareCamera.message = Would you like to share your camera with %S?
-getUserMedia.shareMicrophone.message = Would you like to share your microphone with %S?
-getUserMedia.shareScreen.message = Would you like to share your screen with %S?
-getUserMedia.shareCameraAndMicrophone.message = Would you like to share your camera and microphone with %S?
-getUserMedia.shareCameraAndAudioCapture.message = Would you like to share your camera and this tab’s audio with %S?
-getUserMedia.shareScreenAndMicrophone.message = Would you like to share your microphone and screen with %S?
-getUserMedia.shareScreenAndAudioCapture.message = Would you like to share this tab’s audio and your screen with %S?
-getUserMedia.shareAudioCapture.message = Would you like to share this tab’s audio with %S?
+getUserMedia.shareCamera2.message = Will you allow %S to use your video camera?
+getUserMedia.shareMicrophone2.message = Will you allow %S to use your microphone?
+getUserMedia.shareScreen2.message = Will you allow %S to see your screen or application window?
+getUserMedia.shareCameraAndMicrophone2.message = Will you allow %S to use your camera and microphone?
+getUserMedia.shareCameraAndAudioCapture2.message = Will you allow %S to use your camera and listen to this tab’s audio?
+getUserMedia.shareScreenAndMicrophone2.message = Will you allow %S to use your microphone and see your screen or application window?
+getUserMedia.shareScreenAndAudioCapture2.message = Will you allow %S to listen to this tab’s audio and see your screen or application window?
+getUserMedia.shareAudioCapture2.message = Will you allow %S to use this tab’s audio?
getUserMedia.selectWindow.label=Window to share:
getUserMedia.selectWindow.accesskey=W
getUserMedia.selectScreen.label=Screen to share:
getUserMedia.selectScreen.accesskey=S
getUserMedia.selectApplication.label=Application to share:
getUserMedia.selectApplication.accesskey=A
getUserMedia.noApplication.label = No Application
getUserMedia.noScreen.label = No Screen
@@ -501,32 +496,35 @@ getUserMedia.shareEntireScreen.label = E
# Example: Screen 1, Screen 2,..
getUserMedia.shareMonitor.label = Screen %S
# LOCALIZATION NOTE (getUserMedia.shareApplicationWindowCount.label):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# Replacement for #1 is the name of the application.
# Replacement for #2 is the number of windows currently displayed by the application.
getUserMedia.shareApplicationWindowCount.label=#1 (#2 window);#1 (#2 windows)
-# LOCALIZATION NOTE (getUserMedia.shareSelectedDevices.label):
-# Semicolon-separated list of plural forms. See:
-# http://developer.mozilla.org/en/docs/Localization_and_Plurals
-# The number of devices can be either one or two.
-getUserMedia.shareSelectedDevices.label = Share Selected Device;Share Selected Devices
-getUserMedia.shareSelectedDevices.accesskey = S
-getUserMedia.shareScreen.label = Share Screen
-getUserMedia.shareApplication.label = Share Selected Application
-getUserMedia.shareWindow.label = Share Selected Window
-getUserMedia.shareSelectedItems.label = Share Selected Items
-getUserMedia.always.label = Always Share
-getUserMedia.always.accesskey = A
-getUserMedia.denyRequest.label = Don’t Share
-getUserMedia.denyRequest.accesskey = D
-getUserMedia.never.label = Never Share
-getUserMedia.never.accesskey = N
+getUserMedia.allowScreen.label = Allow Screen Access
+getUserMedia.allowWindow.label = Allow Window Access
+getUserMedia.allowApplication.label = Allow Application Access
+getUserMedia.allowAccess.label = Allow Access
+getUserMedia.allowAccess.accesskey = A
+getUserMedia.dontAllow.label = Don’t Allow
+getUserMedia.dontAllow.accesskey = D
+getUserMedia.remember=Remember this decision
+# LOCALIZATION NOTE (getUserMedia.dontAllowHTTP,
+# getUserMedia.dontAllowHTTPAudio,
+# getUserMedia.dontAllowHTTPVideo,
+# getUserMedia.dontAllowScreenAlways,
+# getUserMedia.dontAllowAudioAlways):
+# %S is brandShortName
+getUserMedia.dontAllowHTTP=Your connection to this site is not secure. %S can not allow permanent access to your devices.
+getUserMedia.dontAllowHTTPAudio=Your connection to this site is not secure. %S can not allow permanent access to your microphone.
+getUserMedia.dontAllowHTTPVideo=Your connection to this site is not secure. %S can not allow permanent access to your camera.
+getUserMedia.dontAllowScreenAlways=%S can not allow permanent access to your screen or application without asking which one to share.
+getUserMedia.dontAllowAudioAlways=%S can not allow permanent access to your tab’s audio without asking which tab to share.
getUserMedia.sharingMenu.label = Tabs sharing devices
getUserMedia.sharingMenu.accesskey = d
# LOCALIZATION NOTE (getUserMedia.sharingMenuCamera
# getUserMedia.sharingMenuMicrophone,
# getUserMedia.sharingMenuAudioCapture,
# getUserMedia.sharingMenuApplication,
# getUserMedia.sharingMenuScreen,
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -221,17 +221,20 @@ this.PermissionPromptPrototype = {
* If omitted, the nsIPermissionManager will not be written to
* when this choice is chosen.
* expireType (Ci.nsIPermissionManager expiration policy, optional)
* The nsIPermissionManager expiration policy that will be associated
* with this choice. For example, Ci.nsIPermissionManager.EXPIRE_SESSION.
*
* If action is not set, expireType will be ignored.
* callback (function, optional)
- * A callback function that will fire if the user makes this choice.
+ * A callback function that will fire if the user makes this choice, with
+ * a single parameter, state. State is an Object that contains the property
+ * checkboxChecked, which identifies whether the checkbox to remember this
+ * decision was checked.
*/
get promptActions() {
return [];
},
/**
* If the prompt will be shown to the user, this callback will
* be called just before. Subclasses may want to override this
@@ -292,24 +295,24 @@ this.PermissionPromptPrototype = {
promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
promptAction.action) {
continue;
}
let action = {
label: promptAction.label,
accessKey: promptAction.accessKey,
- callback: () => {
+ callback: state => {
if (promptAction.callback) {
promptAction.callback();
}
if (this.permissionKey) {
// Remember permissions.
- if (promptAction.action) {
+ if (state && state.checkboxChecked && promptAction.action) {
Services.perms.addFromPrincipal(this.principal,
this.permissionKey,
promptAction.action,
promptAction.expireType);
}
// Grant permission if action is null or ALLOW_ACTION.
if (!promptAction.action ||
@@ -329,17 +332,19 @@ this.PermissionPromptPrototype = {
popupNotificationActions[0] : null;
let secondaryActions = popupNotificationActions.splice(1);
let options = this.popupOptions;
if (!options.hasOwnProperty('displayURI') || options.displayURI) {
options.displayURI = this.principal.URI;
}
+ // Permission prompts are always persistent and don't have a close button.
options.persistent = true;
+ options.hideClose = true;
this.onBeforeShow();
chromeWin.PopupNotifications.show(this.browser,
this.notificationID,
this.message,
this.anchorID,
mainAction,
secondaryActions,
@@ -406,89 +411,95 @@ GeolocationPermissionPrompt.prototype =
__proto__: PermissionPromptForRequestPrototype,
get permissionKey() {
return "geo";
},
get popupOptions() {
let pref = "browser.geolocation.warning.infoURL";
- return {
+ let options = {
learnMoreURL: Services.urlFormatter.formatURLPref(pref),
+ displayURI: false
};
+
+ if (this.principal.URI.schemeIs("file")) {
+ options.checkbox = { show: false };
+ } else {
+ // Don't offer "always remember" action in PB mode
+ options.checkbox = {
+ show: !PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal)
+ };
+ }
+
+ if (options.checkbox.show) {
+ options.checkbox.label = gBrowserBundle.GetStringFromName("geolocation.remember");
+ }
+
+ return options;
},
get notificationID() {
return "geolocation";
},
get anchorID() {
return "geo-notification-icon";
},
get message() {
let message;
if (this.principal.URI.schemeIs("file")) {
- message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile2");
+ message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile3");
} else {
- message = gBrowserBundle.GetStringFromName("geolocation.shareWithSite2");
+ let host = this.principal.URI.host;
+ message = gBrowserBundle.formatStringFromName("geolocation.shareWithSite3", [host], 1);
}
return message;
},
get promptActions() {
// We collect Telemetry data on Geolocation prompts and how users
// respond to them. The probe keys are a bit verbose, so let's alias them.
const SHARE_LOCATION =
Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_SHARE_LOCATION;
const ALWAYS_SHARE =
Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_ALWAYS_SHARE;
const NEVER_SHARE =
Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE;
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
- let actions = [{
- label: gBrowserBundle.GetStringFromName("geolocation.shareLocation"),
+ return [{
+ label: gBrowserBundle.GetStringFromName("geolocation.allowLocation"),
accessKey:
- gBrowserBundle.GetStringFromName("geolocation.shareLocation.accesskey"),
+ gBrowserBundle.GetStringFromName("geolocation.allowLocation.accesskey"),
action: null,
expireType: null,
- callback: function() {
- secHistogram.add(SHARE_LOCATION);
+ callback: function(state) {
+ if (state && state.checkboxChecked) {
+ secHistogram.add(ALWAYS_SHARE);
+ } else {
+ secHistogram.add(SHARE_LOCATION);
+ }
+ },
+ }, {
+ label: gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("geolocation.dontAllowLocation.accesskey"),
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal) ?
+ Ci.nsIPermissionManager.EXPIRE_SESSION :
+ null,
+ callback: function(state) {
+ if (state && state.checkboxChecked) {
+ secHistogram.add(NEVER_SHARE);
+ }
},
}];
-
- if (!this.principal.URI.schemeIs("file")) {
- // Always share location action.
- actions.push({
- label: gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation"),
- accessKey:
- gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation.accesskey"),
- action: Ci.nsIPermissionManager.ALLOW_ACTION,
- expireType: null,
- callback: function() {
- secHistogram.add(ALWAYS_SHARE);
- },
- });
-
- // Never share location action.
- actions.push({
- label: gBrowserBundle.GetStringFromName("geolocation.neverShareLocation"),
- accessKey:
- gBrowserBundle.GetStringFromName("geolocation.neverShareLocation.accesskey"),
- action: Ci.nsIPermissionManager.DENY_ACTION,
- expireType: null,
- callback: function() {
- secHistogram.add(NEVER_SHARE);
- },
- });
- }
-
- return actions;
},
onBeforeShow() {
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
const SHOW_REQUEST = Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST;
secHistogram.add(SHOW_REQUEST);
},
};
@@ -513,81 +524,67 @@ DesktopNotificationPermissionPrompt.prot
get permissionKey() {
return "desktop-notification";
},
get popupOptions() {
let learnMoreURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
- // The eventCallback is bound to the Notification that's being
- // shown. We'll stash a reference to this in the closure so that
- // the request can be cancelled.
- let prompt = this;
+ let checkbox = {
+ show: true,
+ checked: true,
+ label: gBrowserBundle.GetStringFromName("webNotifications.remember")
+ };
- let eventCallback = function(type) {
- if (type == "dismissed") {
- // Bug 1259148: Hide the doorhanger icon. Unlike other permission
- // doorhangers, the user can't restore the doorhanger using the icon
- // in the location bar. Instead, the site will be notified that the
- // doorhanger was dismissed.
- this.remove();
- prompt.request.cancel();
- }
- };
+ // In PB mode, the "always remember" checkbox should only remember for the
+ // session.
+ if (PrivateBrowsingUtils.isWindowPrivate(this.browser.ownerGlobal)) {
+ checkbox.label =
+ gBrowserBundle.GetStringFromName("webNotifications.rememberForSession");
+ }
return {
learnMoreURL,
- eventCallback,
+ checkbox,
+ displayURI: false
};
},
get notificationID() {
return "web-notifications";
},
get anchorID() {
return "web-notifications-notification-icon";
},
get message() {
- return gBrowserBundle.GetStringFromName("webNotifications.receiveFromSite");
+ let host = this.principal.URI.host;
+ return gBrowserBundle.formatStringFromName("webNotifications.receiveFromSite2", [host], 1);
},
get promptActions() {
- let promptActions;
- // Only show "allow for session" in PB mode, we don't
- // support "allow for session" in non-PB mode.
- if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
- promptActions = [
- {
- label: gBrowserBundle.GetStringFromName("webNotifications.receiveForSession"),
- accessKey:
- gBrowserBundle.GetStringFromName("webNotifications.receiveForSession.accesskey"),
- action: Ci.nsIPermissionManager.ALLOW_ACTION,
- expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
- }
- ];
- } else {
- promptActions = [
- {
- label: gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive"),
- accessKey:
- gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive.accesskey"),
- action: Ci.nsIPermissionManager.ALLOW_ACTION,
- expireType: null,
- },
- {
- label: gBrowserBundle.GetStringFromName("webNotifications.neverShow"),
- accessKey:
- gBrowserBundle.GetStringFromName("webNotifications.neverShow.accesskey"),
- action: Ci.nsIPermissionManager.DENY_ACTION,
- expireType: null,
- },
- ];
- }
-
- return promptActions;
+ return [
+ {
+ label: gBrowserBundle.GetStringFromName("webNotifications.allow"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("webNotifications.allow.accesskey"),
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: PrivateBrowsingUtils.isBrowserPrivate(this.browser) ?
+ Ci.nsIPermissionManager.EXPIRE_SESSION :
+ null,
+ },
+ {
+ label: gBrowserBundle.GetStringFromName("webNotifications.dontAllow"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("webNotifications.dontAllow.accesskey"),
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: PrivateBrowsingUtils.isBrowserPrivate(this.browser) ?
+ Ci.nsIPermissionManager.EXPIRE_SESSION :
+ null,
+ },
+ ];
},
};
PermissionUI.DesktopNotificationPermissionPrompt =
DesktopNotificationPermissionPrompt;
--- a/browser/modules/test/browser_PermissionUI.js
+++ b/browser/modules/test/browser_PermissionUI.js
@@ -71,18 +71,22 @@ function clickMainAction() {
* @return {Promise}
* Resolves once the panel has fired the "popuphidden"
* event.
*/
function clickSecondaryAction(index) {
let removePromise =
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
let popupNotification = getPopupNotificationNode();
- let menuitems = popupNotification.children;
- menuitems[index].click();
+ if (index == 0) {
+ popupNotification.secondaryButton.click();
+ } else {
+ let menuitems = popupNotification.children;
+ menuitems[index].click();
+ }
return removePromise;
}
/**
* Makes sure that 1 (and only 1) <xul:popupnotification> is being displayed
* by PopupNotification, and then returns that <xul:popupnotification>.
*
* @return {<xul:popupnotification>}
@@ -250,16 +254,23 @@ add_task(function* test_with_permission_
let TestPrompt = {
__proto__: PermissionUI.PermissionPromptForRequestPrototype,
request: mockRequest,
notificationID: kTestNotificationID,
permissionKey: kTestPermissionKey,
message: kTestMessage,
promptActions: [mainAction, secondaryAction],
+ popupOptions: {
+ checkbox: {
+ label: "Remember this decision",
+ show: true,
+ checked: true
+ }
+ }
};
let shownPromise =
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
TestPrompt.prompt();
yield shownPromise;
let notification =
PopupNotifications.getNotification(kTestNotificationID, browser);
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -13,16 +13,20 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
+ return Services.strings.createBundle("chrome://branding/locale/brand.properties");
+});
+
this.webrtcUI = {
init: function () {
Services.obs.addObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished", false);
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
ppmm.addMessageListener("webrtc:UpdatingIndicators", this);
ppmm.addMessageListener("webrtc:UpdateGlobalIndicators", this);
@@ -161,17 +165,17 @@ this.webrtcUI = {
document.getElementById("webRTC-all-windows-shared").hidden = type != "Screen";
// If we are also requesting audio in addition to screen sharing,
// always use a generic label.
if (!document.getElementById("webRTC-selectMicrophone").hidden)
type = "";
let bundle = document.defaultView.gNavigatorBundle;
- let stringId = "getUserMedia.share" + (type || "SelectedItems") + ".label";
+ let stringId = "getUserMedia.allow" + (type || "Access") + ".label";
let popupnotification = aMenuList.parentNode.parentNode;
popupnotification.setAttribute("buttonlabel", bundle.getString(stringId));
},
receiveMessage: function(aMessage) {
switch (aMessage.name) {
// Add-ons can override stock permission behavior by doing:
@@ -290,79 +294,71 @@ function prompt(aBrowser, aRequest) {
let {audioDevices: audioDevices, videoDevices: videoDevices,
sharingScreen: sharingScreen, sharingAudio: sharingAudio,
requestTypes: requestTypes} = aRequest;
let uri = Services.io.newURI(aRequest.documentURI, null, null);
let host = getHost(uri);
let chromeDoc = aBrowser.ownerDocument;
let chromeWin = chromeDoc.defaultView;
let stringBundle = chromeWin.gNavigatorBundle;
- let stringId = "getUserMedia.share" + requestTypes.join("And") + ".message";
+ let stringId = "getUserMedia.share" + requestTypes.join("And") + "2.message";
let message = stringBundle.getFormattedString(stringId, [host]);
- let mainLabel;
- if (sharingScreen || sharingAudio) {
- mainLabel = stringBundle.getString("getUserMedia.shareSelectedItems.label");
- } else {
- let string = stringBundle.getString("getUserMedia.shareSelectedDevices.label");
- mainLabel = PluralForm.get(requestTypes.length, string);
- }
-
let notification; // Used by action callbacks.
let mainAction = {
- label: mainLabel,
- accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
+ label: stringBundle.getString("getUserMedia.allowAccess.label"),
+ accessKey: stringBundle.getString("getUserMedia.allowAccess.accesskey"),
// The real callback will be set during the "showing" event. The
// empty function here is so that PopupNotifications.show doesn't
// reject the action.
callback: function() {}
};
let secondaryActions = [
{
- label: stringBundle.getString("getUserMedia.denyRequest.label"),
- accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
- callback: function () {
+ label: stringBundle.getString("getUserMedia.dontAllow.label"),
+ accessKey: stringBundle.getString("getUserMedia.dontAllow.accesskey"),
+ callback: function (aState) {
denyRequest(notification.browser, aRequest);
+ if (aState && aState.checkboxChecked) {
+ let perms = Services.perms;
+ if (audioDevices.length)
+ perms.add(uri, "microphone", perms.DENY_ACTION);
+ if (videoDevices.length)
+ perms.add(uri, "camera", perms.DENY_ACTION);
+ }
}
}
];
- // Bug 1037438: implement 'never' for screen sharing.
- if (!sharingScreen && !sharingAudio) {
- secondaryActions.push({
- label: stringBundle.getString("getUserMedia.never.label"),
- accessKey: stringBundle.getString("getUserMedia.never.accesskey"),
- callback: function () {
- denyRequest(notification.browser, aRequest);
- // Let someone save "Never" for http sites so that they can be stopped from
- // bothering you with doorhangers.
- let perms = Services.perms;
- if (audioDevices.length)
- perms.add(uri, "microphone", perms.DENY_ACTION);
- if (videoDevices.length)
- perms.add(uri, "camera", perms.DENY_ACTION);
- }
- });
- }
- if (aRequest.secure && !sharingScreen && !sharingAudio) {
- // Don't show the 'Always' action if the connection isn't secure, or for
- // screen/audio sharing (because we can't guess which window the user wants
- // to share without prompting).
- secondaryActions.unshift({
- label: stringBundle.getString("getUserMedia.always.label"),
- accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
- callback: function (aState) {
- mainAction.callback(aState, true);
- }
- });
+ let productName = gBrandBundle.GetStringFromName("brandShortName");
+ let id = "getUserMedia.dontAllowHTTP";
+ if (sharingScreen) {
+ id = "getUserMedia.dontAllowScreenAlways";
+ } else if (sharingAudio) {
+ id = "getUserMedia.dontAllowAudioAlways";
+ } else if (audioDevices.length && !videoDevices.length) {
+ id = "getUserMedia.dontAllowHTTPAudio";
+ } else if (!audioDevices.length && videoDevices.length) {
+ id = "getUserMedia.dontAllowHTTPVideo";
}
let options = {
persistent: true,
+ hideClose: true,
+ checkbox: {
+ label: stringBundle.getString("getUserMedia.remember"),
+ checkedState: {
+ // Disable the 'Allow' action if the connection isn't secure, or for
+ // screen/audio sharing (because we can't guess which window the user wants
+ // to share without prompting).
+ disableMainAction: !aRequest.secure || sharingScreen || sharingAudio,
+ warningLabel: stringBundle.getFormattedString(id, [productName])
+ }
+ },
eventCallback: function(aTopic, aNewBrowser) {
if (aTopic == "swapping")
return true;
let chromeDoc = this.browser.ownerDocument;
if (aTopic == "shown") {
let popupId = "Devices";
@@ -520,57 +516,58 @@ function prompt(aBrowser, aRequest) {
if (sharingScreen)
listScreenShareDevices(windowMenupopup, videoDevices);
else
listDevices(camMenupopup, videoDevices);
if (!sharingAudio)
listDevices(micMenupopup, audioDevices);
- this.mainAction.callback = function(aState, aRemember) {
+ this.mainAction.callback = function(aState) {
+ let remember = aState && aState.checkboxChecked;
let allowedDevices = [];
let perms = Services.perms;
if (videoDevices.length) {
let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
let videoDeviceIndex = chromeDoc.getElementById(listId).value;
let allowCamera = videoDeviceIndex != "-1";
if (allowCamera) {
allowedDevices.push(videoDeviceIndex);
// Session permission will be removed after use
// (it's really one-shot, not for the entire session)
perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
perms.EXPIRE_SESSION);
}
- if (aRemember) {
+ if (remember) {
perms.add(uri, "camera",
allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
}
}
if (audioDevices.length) {
if (!sharingAudio) {
let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
let allowMic = audioDeviceIndex != "-1";
if (allowMic)
allowedDevices.push(audioDeviceIndex);
- if (aRemember) {
+ if (remember) {
perms.add(uri, "microphone",
allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
}
} else {
// Only one device possible for audio capture.
allowedDevices.push(0);
}
}
if (!allowedDevices.length) {
denyRequest(notification.browser, aRequest);
return;
}
- if (aRemember) {
+ if (remember) {
// Remember on which URIs we set persistent permissions so that we
// can remove them if the user clicks 'Stop Sharing'.
aBrowser._devicePermissionURIs = aBrowser._devicePermissionURIs || [];
aBrowser._devicePermissionURIs.push(uri);
}
let mm = notification.browser.messageManager;
mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
--- a/dom/indexedDB/test/head.js
+++ b/dom/indexedDB/test/head.js
@@ -52,16 +52,22 @@ function triggerMainCommand(popup)
function triggerSecondaryCommand(popup, index)
{
info("triggering secondary command, " + index);
let notifications = popup.childNodes;
ok(notifications.length > 0, "at least one notification displayed");
let notification = notifications[0];
+ if (index == 0) {
+ EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+ return;
+ }
+
+ // Extra secondary actions appear in a menu.
notification.button.nextSibling.nextSibling.focus();
popup.addEventListener("popupshown", function () {
popup.removeEventListener("popupshown", arguments.callee, false);
// Press down until the desired command is selected
for (let i = 0; i <= index; i++)
EventUtils.synthesizeKey("VK_DOWN", {});
--- a/dom/notification/test/browser/browser_permission_dismiss.js
+++ b/dom/notification/test/browser/browser_permission_dismiss.js
@@ -20,16 +20,19 @@ function clickDoorhangerButton(aButtonIn
let notifications = popup.owner.panel.childNodes;
ok(notifications.length > 0, "at least one notification displayed");
ok(true, notifications.length + " notification(s)");
let notification = notifications[0];
if (aButtonIndex == -1) {
ok(true, "Triggering main action");
notification.button.doCommand();
+ } else if (aButtonIndex == 0) {
+ ok(true, "Triggering secondary action");
+ notification.secondaryButton.doCommand();
} else if (aButtonIndex <= popup.secondaryActions.length) {
ok(true, "Triggering secondary action " + aButtonIndex);
notification.childNodes[aButtonIndex].doCommand();
}
}
/**
* Opens a tab which calls `Notification.requestPermission()` with a callback
@@ -93,21 +96,8 @@ add_task(function* test_requestPermissio
ok(!PopupNotifications.getNotification("web-notifications"),
"Should remove the doorhanger notification icon if denied");
is(Services.perms.testPermission(ORIGIN_URI, PERMISSION_NAME),
Services.perms.DENY_ACTION,
"Check permission in perm. manager");
});
-
-add_task(function* test_requestPermission_dismissed() {
- yield tabWithRequest(function() {
- PopupNotifications.panel.hidePopup();
- }, "default");
-
- ok(!PopupNotifications.getNotification("web-notifications"),
- "Should remove the doorhanger notification icon if dismissed");
-
- is(Services.perms.testPermission(ORIGIN_URI, PERMISSION_NAME),
- Services.perms.UNKNOWN_ACTION,
- "Check permission in perm. manager");
-});
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -767,35 +767,40 @@ LoginManagerPrompter.prototype = {
* @param {string} type
* This is "password-save" or "password-change" depending on the
* original notification type. This is used for telemetry and tests.
*/
_showLoginCaptureDoorhanger(login, type) {
let { browser } = this._getNotifyWindow();
let saveMsgNames = {
- prompt: login.username === "" ? "rememberLoginMsgNoUser"
- : "rememberLoginMsg",
- buttonLabel: "rememberLoginButtonText",
- buttonAccessKey: "rememberLoginButtonAccessKey",
+ prompt: login.username === "" ? "saveLoginMsgNoUser"
+ : "saveLoginMsg",
+ buttonLabel: "saveLoginButtonAllow.label",
+ buttonAccessKey: "saveLoginButtonAllow.accesskey",
+ secondaryButtonLabel: "saveLoginButtonDeny.label",
+ secondaryButtonAccessKey: "saveLoginButtonDeny.accesskey",
};
let changeMsgNames = {
prompt: login.username === "" ? "updateLoginMsgNoUser"
: "updateLoginMsg",
buttonLabel: "updateLoginButtonText",
buttonAccessKey: "updateLoginButtonAccessKey",
+ secondaryButtonLabel: "updateLoginButtonDeny.label",
+ secondaryButtonAccessKey: "updateLoginButtonDeny.accesskey",
};
let initialMsgNames = type == "password-save" ? saveMsgNames
: changeMsgNames;
let brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
let brandShortName = brandBundle.GetStringFromName("brandShortName");
- let promptMsg = type == "password-save" ? this._getLocalizedString(saveMsgNames.prompt, [brandShortName])
+ let host = this._getShortDisplayHost(login.hostname);
+ let promptMsg = type == "password-save" ? this._getLocalizedString(saveMsgNames.prompt, [brandShortName, host])
: this._getLocalizedString(changeMsgNames.prompt);
let histogramName = type == "password-save" ? "PWMGR_PROMPT_REMEMBER_ACTION"
: "PWMGR_PROMPT_UPDATE_ACTION";
let histogram = Services.telemetry.getHistogramById(histogramName);
histogram.add(PROMPT_DISPLAYED);
let chromeDoc = browser.ownerDocument;
@@ -914,58 +919,68 @@ LoginManagerPrompter.prototype = {
} else {
this._updateLogin(logins[0], login);
}
} else {
Cu.reportError("Unexpected match of multiple logins.");
}
};
- // The main action is the "Remember" or "Update" button.
+ // The main action is the "Save" or "Update" button.
let mainAction = {
label: this._getLocalizedString(initialMsgNames.buttonLabel),
accessKey: this._getLocalizedString(initialMsgNames.buttonAccessKey),
callback: () => {
histogram.add(PROMPT_ADD_OR_UPDATE);
if (histogramName == "PWMGR_PROMPT_REMEMBER_ACTION") {
Services.obs.notifyObservers(null, 'LoginStats:NewSavedPassword', null);
}
readDataFromUI();
persistData();
browser.focus();
}
};
- // Include a "Never for this site" button when saving a new password.
- let secondaryActions = type == "password-save" ? [{
- label: this._getLocalizedString("notifyBarNeverRememberButtonText"),
- accessKey: this._getLocalizedString("notifyBarNeverRememberButtonAccessKey"),
+ let secondaryActions = [{
+ label: this._getLocalizedString(initialMsgNames.secondaryButtonLabel),
+ accessKey: this._getLocalizedString(initialMsgNames.secondaryButtonAccessKey),
callback: () => {
- histogram.add(PROMPT_NEVER);
- Services.logins.setLoginSavingEnabled(login.hostname, false);
+ histogram.add(PROMPT_NOTNOW);
browser.focus();
}
- }] : null;
+ }];
+ // Include a "Never for this site" button when saving a new password.
+ if (type == "password-save") {
+ secondaryActions.push({
+ label: this._getLocalizedString("notifyBarNeverRememberButtonText2"),
+ accessKey: this._getLocalizedString("notifyBarNeverRememberButtonAccessKey"),
+ callback: () => {
+ histogram.add(PROMPT_NEVER);
+ Services.logins.setLoginSavingEnabled(login.hostname, false);
+ browser.focus();
+ }
+ });
+ }
let usernamePlaceholder = this._getLocalizedString("noUsernamePlaceholder");
let togglePasswordLabel = this._getLocalizedString("togglePasswordLabel");
let togglePasswordAccessKey = this._getLocalizedString("togglePasswordAccessKey");
this._getPopupNote().show(
browser,
"password",
promptMsg,
"password-notification-icon",
mainAction,
secondaryActions,
{
- displayURI: Services.io.newURI(login.hostname, null, null),
persistWhileVisible: true,
persistent: true,
passwordNotificationType: type,
+ hideClose: true,
eventCallback: function (topic) {
switch (topic) {
case "showing":
currentNotification = this;
chromeDoc.getElementById("password-notification-password")
.removeAttribute("focused");
chromeDoc.getElementById("password-notification-username")
.removeAttribute("focused");
@@ -1020,17 +1035,17 @@ LoginManagerPrompter.prototype = {
* The login captured from the form.
*/
_showSaveLoginNotification : function (aNotifyObj, aLogin) {
// Ugh. We can't use the strings from the popup window, because they
// have the access key marked in the string (eg "Mo&zilla"), along
// with some weird rules for handling access keys that do not occur
// in the string, for L10N. See commonDialog.js's setLabelForNode().
var neverButtonText =
- this._getLocalizedString("notifyBarNeverRememberButtonText");
+ this._getLocalizedString("notifyBarNeverRememberButtonText2");
var neverButtonAccessKey =
this._getLocalizedString("notifyBarNeverRememberButtonAccessKey");
var rememberButtonText =
this._getLocalizedString("notifyBarRememberPasswordButtonText");
var rememberButtonAccessKey =
this._getLocalizedString("notifyBarRememberPasswordButtonAccessKey");
var displayHost = this._getShortDisplayHost(aLogin.hostname);
--- a/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
@@ -375,37 +375,37 @@ add_task(function* test_changePLoginOnPF
add_task(function* test_checkUPSaveText() {
info("Check text on a user+pass notification popup");
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
- // Check the text, which comes from the localized saveLoginText string.
+ // Check the text, which comes from the localized saveLoginMsg string.
let notificationText = notif.message;
- let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this login?";
+ let expectedText = "Would you like " + BRAND_SHORT_NAME + " to save this login for example.com?";
is(expectedText, notificationText, "Checking text: " + notificationText);
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_checkPSaveText() {
info("Check text on a pass-only notification popup");
yield testSubmittingLoginForm("subtst_notifications_6.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
- // Check the text, which comes from the localized saveLoginTextNoUser string.
+ // Check the text, which comes from the localized saveLoginMsgNoUser string.
let notificationText = notif.message;
- let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this password?";
+ let expectedText = "Would you like " + BRAND_SHORT_NAME + " to save this password for example.com?";
is(expectedText, notificationText, "Checking text: " + notificationText);
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_capture2pw0un() {
--- a/toolkit/components/passwordmgr/test/browser/head.js
+++ b/toolkit/components/passwordmgr/test/browser/head.js
@@ -62,17 +62,17 @@ function checkOnlyLoginWasUsedTwice({ ju
} else {
is(logins[0].timeCreated, logins[0].timePasswordChanged, "timeChanged not updated");
}
}
// Begin popup notification (doorhanger) functions //
const REMEMBER_BUTTON = 0;
-const NEVER_BUTTON = 1;
+const NEVER_BUTTON = 2;
const CHANGE_BUTTON = 0;
const DONT_CHANGE_BUTTON = 1;
/**
* Checks if we have a password capture popup notification
* of the right type and with the right label.
*
@@ -83,17 +83,17 @@ const DONT_CHANGE_BUTTON = 1;
function getCaptureDoorhanger(aKind, popupNotifications = PopupNotifications) {
ok(true, "Looking for " + aKind + " popup notification");
let notification = popupNotifications.getNotification("password");
if (notification) {
is(notification.options.passwordNotificationType, aKind, "Notification type matches.");
if (aKind == "password-change") {
is(notification.mainAction.label, "Update", "Main action label matches update doorhanger.");
} else if (aKind == "password-save") {
- is(notification.mainAction.label, "Remember", "Main action label matches save doorhanger.");
+ is(notification.mainAction.label, "Save", "Main action label matches save doorhanger.");
}
}
return notification;
}
/**
* Clicks the specified popup notification button.
*
@@ -107,19 +107,22 @@ function clickDoorhangerButton(aPopup, a
let notifications = aPopup.owner.panel.childNodes;
ok(notifications.length > 0, "at least one notification displayed");
ok(true, notifications.length + " notification(s)");
let notification = notifications[0];
if (aButtonIndex == 0) {
ok(true, "Triggering main action");
notification.button.doCommand();
+ } else if (aButtonIndex == 1) {
+ ok(true, "Triggering secondary action");
+ notification.secondaryButton.doCommand();
} else if (aButtonIndex <= aPopup.secondaryActions.length) {
ok(true, "Triggering secondary action " + aButtonIndex);
- notification.childNodes[aButtonIndex].doCommand();
+ notification.childNodes[aButtonIndex - 1].doCommand();
}
}
/**
* Checks the doorhanger's username and password.
*
* @param {String} username The username.
* @param {String} password The password.
--- a/toolkit/content/widgets/notification.xml
+++ b/toolkit/content/widgets/notification.xml
@@ -497,60 +497,62 @@
<xul:label class="popup-notification-origin header"
xbl:inherits="value=origin,tooltiptext=origin"
crop="center"/>
<xul:description class="popup-notification-description"
xbl:inherits="xbl:text=label,popupid"/>
</xul:vbox>
<xul:toolbarbutton anonid="closebutton"
class="messageCloseButton close-icon popup-notification-closebutton tabbable"
- xbl:inherits="oncommand=closebuttoncommand"
+ xbl:inherits="oncommand=closebuttoncommand,hidden=hideclose"
tooltiptext="&closeNotification.tooltip;"/>
</xul:hbox>
<children includes="popupnotificationcontent"/>
<xul:label class="text-link popup-notification-learnmore-link"
xbl:inherits="onclick=learnmoreclick,href=learnmoreurl">&learnMore;</xul:label>
<xul:checkbox anonid="checkbox"
xbl:inherits="hidden=checkboxhidden,checked=checkboxchecked,label=checkboxlabel,oncommand=checkboxcommand" />
<xul:description class="popup-notification-warning" xbl:inherits="hidden=warninghidden,xbl:text=warninglabel"/>
</xul:vbox>
</xul:hbox>
- <xul:hbox pack="end" class="popup-notification-button-container">
+ <xul:hbox class="popup-notification-button-container">
<children includes="button"/>
- <xul:hbox class="popup-notification-button-wrapper">
- <xul:button anonid="button"
- class="popup-notification-button"
- default="true"
- xbl:inherits="oncommand=buttoncommand,onpopupshown=buttonpopupshown,label=buttonlabel,accesskey=buttonaccesskey,disabled=mainactiondisabled"/>
- <xul:toolbarseparator/>
- <xul:button type="menu"
- class="popup-notification-button popup-notification-dropmarker">
- <xul:menupopup anonid="menupopup"
- position="after_end"
- xbl:inherits="oncommand=menucommand">
- <children/>
- <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
- label="&closeNotificationItem.label;"
- xbl:inherits="oncommand=closeitemcommand,hidden=hidenotnow"/>
- </xul:menupopup>
- </xul:button>
- </xul:hbox>
+ <xul:button anonid="secondarybutton"
+ class="popup-notification-button popup-notification-button-secondary"
+ xbl:inherits="oncommand=secondarybuttoncommand,onpopupshown=buttonpopupshown,label=secondarybuttonlabel,accesskey=secondarybuttonaccesskey,disabled=secondarybuttondisabled"/>
+ <xul:toolbarseparator xbl:inherits="hidden=hidemenu"/>
+ <xul:button type="menu"
+ class="popup-notification-button popup-notification-button-secondary popup-notification-dropmarker"
+ xbl:inherits="hidden=hidemenu">
+ <xul:menupopup anonid="menupopup"
+ position="after_end"
+ xbl:inherits="oncommand=menucommand">
+ <children/>
+ </xul:menupopup>
+ </xul:button>
+ <xul:button anonid="button"
+ class="popup-notification-button"
+ default="true"
+ xbl:inherits="oncommand=buttoncommand,onpopupshown=buttonpopupshown,label=buttonlabel,accesskey=buttonaccesskey,disabled=mainactiondisabled"/>
</xul:hbox>
</content>
<resources>
<stylesheet src="chrome://global/skin/notification.css"/>
</resources>
<implementation>
<field name="checkbox" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "checkbox");
</field>
<field name="closebutton" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "closebutton");
</field>
<field name="button" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "button");
</field>
+ <field name="secondaryButton" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "secondarybutton");
+ </field>
<field name="menupopup" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "menupopup");
</field>
</implementation>
</binding>
</bindings>
--- a/toolkit/locales/en-US/chrome/global/notification.dtd
+++ b/toolkit/locales/en-US/chrome/global/notification.dtd
@@ -1,11 +1,9 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY closeNotification.tooltip "Close this message">
-<!ENTITY closeNotificationItem.label "Not Now">
-
<!ENTITY checkForUpdates "Check for updates…">
<!ENTITY learnMore "Learn more…">
--- a/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
+++ b/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
@@ -1,41 +1,46 @@
# 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/.
rememberValue = Use Password Manager to remember this value.
rememberPassword = Use Password Manager to remember this password.
savePasswordTitle = Confirm
-# LOCALIZATION NOTE (rememberLoginMsg, rememberLoginMsgNoUser): %S is brandShortName
-rememberLoginMsg = Would you like %S to remember this login?
-rememberLoginMsgNoUser = Would you like %S to remember this password?
-rememberLoginButtonText = Remember
-rememberLoginButtonAccessKey = R
+# LOCALIZATION NOTE (rememberLoginMsg, rememberLoginMsgNoUser):
+# %1$S is brandShortName, %2$S is the login's hostname.
+saveLoginMsg = Would you like %1$S to save this login for %2$S?
+saveLoginMsgNoUser = Would you like %1$S to save this password for %2$S?
+saveLoginButtonAllow.label = Save
+saveLoginButtonAllow.accesskey = S
+saveLoginButtonDeny.label = Don’t Save
+saveLoginButtonDeny.accesskey = D
updateLoginMsg = Would you like to update this login?
updateLoginMsgNoUser = Would you like to update this password?
updateLoginButtonText = Update
updateLoginButtonAccessKey = U
+updateLoginButtonDeny.label = Don’t Update
+updateLoginButtonDeny.accesskey = D
# LOCALIZATION NOTE (rememberPasswordMsg):
# 1st string is the username for the login, 2nd is the login's hostname.
# Note that long usernames may be truncated.
rememberPasswordMsg = Would you like to remember the password for “%1$S” on %2$S?
# LOCALIZATION NOTE (rememberPasswordMsgNoUsername):
# String is the login's hostname.
rememberPasswordMsgNoUsername = Would you like to remember the password on %S?
# LOCALIZATION NOTE (noUsernamePlaceholder):
# This is displayed in place of the username when it is missing.
noUsernamePlaceholder=No username
togglePasswordLabel=Show password
togglePasswordAccessKey=S
notNowButtonText = &Not Now
notifyBarNotNowButtonText = Not Now
notifyBarNotNowButtonAccessKey = N
neverForSiteButtonText = Ne&ver for This Site
-notifyBarNeverRememberButtonText = Never Remember Password for This Site
+notifyBarNeverRememberButtonText2 = Never Save
notifyBarNeverRememberButtonAccessKey = e
rememberButtonText = &Remember
notifyBarRememberPasswordButtonText = Remember Password
notifyBarRememberPasswordButtonAccessKey = R
passwordChangeTitle = Confirm Password Change
# LOCALIZATION NOTE (updatePasswordMsg):
# String is the username for the login.
updatePasswordMsg = Would you like to update the saved password for “%S”?
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -26,17 +26,16 @@ const PREF_SECURITY_DELAY = "security.no
const TELEMETRY_STAT_OFFERED = 0;
const TELEMETRY_STAT_ACTION_1 = 1;
const TELEMETRY_STAT_ACTION_2 = 2;
const TELEMETRY_STAT_ACTION_3 = 3;
const TELEMETRY_STAT_ACTION_LAST = 4;
const TELEMETRY_STAT_DISMISSAL_CLICK_ELSEWHERE = 5;
const TELEMETRY_STAT_DISMISSAL_LEAVE_PAGE = 6;
const TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON = 7;
-const TELEMETRY_STAT_DISMISSAL_NOT_NOW = 8;
const TELEMETRY_STAT_OPEN_SUBMENU = 10;
const TELEMETRY_STAT_LEARN_MORE = 11;
const TELEMETRY_STAT_REOPENED_OFFSET = 20;
var popupNotificationsMap = new WeakMap();
var gNotificationParents = new WeakMap;
@@ -347,16 +346,18 @@ PopupNotifications.prototype = {
* will be removed.
* neverShow: Indicate that no popup should be shown for this
* notification. Useful for just showing the anchor icon.
* removeOnDismissal:
* Notifications with this parameter set to true will be
* removed when they would have otherwise been dismissed
* (i.e. any time the popup is closed due to user
* interaction).
+ * hideClose: Indicate that the little close button in the corner of
+ * the panel should be hidden.
* checkbox: An object that allows you to add a checkbox and
* control its behavior with these fields:
* label:
* (required) Label to be shown next to the checkbox.
* checked:
* (optional) Whether the checkbox should be checked
* by default. Defaults to false.
* checkedState:
@@ -367,21 +368,16 @@ PopupNotifications.prototype = {
* Defaults to false.
* warningLabel:
* (optional) A (warning) text that is shown below the
* checkbox. Pass null to hide.
* uncheckedState:
* (optional) An object that allows you to customize
* the notification state when the checkbox is not checked.
* Has the same attributes as checkedState.
- * hideNotNow: If true, indicates that the 'Not Now' menuitem should
- * not be shown. If 'Not Now' is hidden, it needs to be
- * replaced by another 'do nothing' item, so providing at
- * least one secondary action is required; and one of the
- * actions needs to have the 'dismiss' property set to true.
* popupIconURL:
* A string. URL of the image to be displayed in the popup.
* Normally specified in CSS using list-style-image and the
* .popup-notification-icon[popupid=...] selector.
* learnMoreURL:
* A string URL. Setting this property will make the
* prompt display a "Learn More" link that, when clicked,
* opens the URL in a new tab.
@@ -401,20 +397,16 @@ PopupNotifications.prototype = {
if (!browser)
throw "PopupNotifications_show: invalid browser";
if (!id)
throw "PopupNotifications_show: invalid ID";
if (mainAction && isInvalidAction(mainAction))
throw "PopupNotifications_show: invalid mainAction";
if (secondaryActions && secondaryActions.some(isInvalidAction))
throw "PopupNotifications_show: invalid secondaryActions";
- if (options && options.hideNotNow &&
- (!secondaryActions || !secondaryActions.length ||
- !secondaryActions.concat(mainAction).some(action => action.dismiss)))
- throw "PopupNotifications_show: 'Not Now' item hidden without replacement";
let notification = new Notification(id, message, anchorID, mainAction,
secondaryActions, browser, this, options);
if (options && options.dismissed)
notification.dismissed = true;
let existingNotification = this.getNotification(id, browser);
@@ -589,18 +581,17 @@ PopupNotifications.prototype = {
_dismiss: function PopupNotifications_dismiss(telemetryReason) {
if (telemetryReason) {
this.nextDismissReason = telemetryReason;
}
// An explicitly dismissed persistent notification effectively becomes
// non-persistent.
if (this.panel.firstChild &&
- (telemetryReason == TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON ||
- telemetryReason == TELEMETRY_STAT_DISMISSAL_NOT_NOW)) {
+ telemetryReason == TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON) {
this.panel.firstChild.notification.options.persistent = false;
}
let browser = this.panel.firstChild &&
this.panel.firstChild.notification.browser;
this.panel.hidePopup();
if (browser)
browser.focus();
@@ -682,25 +673,23 @@ PopupNotifications.prototype = {
popupnotification.setAttribute("closebuttoncommand", `PopupNotifications._dismiss(${TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON});`);
if (n.mainAction) {
popupnotification.setAttribute("buttonlabel", n.mainAction.label);
popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonEvent(event, 'buttoncommand');");
popupnotification.setAttribute("buttonpopupshown", "PopupNotifications._onButtonEvent(event, 'buttonpopupshown');");
popupnotification.setAttribute("learnmoreclick", "PopupNotifications._onButtonEvent(event, 'learnmoreclick');");
popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
- popupnotification.setAttribute("closeitemcommand", `PopupNotifications._dismiss(${TELEMETRY_STAT_DISMISSAL_NOT_NOW});event.stopPropagation();`);
} else {
popupnotification.removeAttribute("buttonlabel");
popupnotification.removeAttribute("buttonaccesskey");
popupnotification.removeAttribute("buttoncommand");
popupnotification.removeAttribute("buttonpopupshown");
popupnotification.removeAttribute("learnmoreclick");
popupnotification.removeAttribute("menucommand");
- popupnotification.removeAttribute("closeitemcommand");
}
if (n.options.popupIconURL)
popupnotification.setAttribute("icon", n.options.popupIconURL);
if (n.options.learnMoreURL)
popupnotification.setAttribute("learnmoreurl", n.options.learnMoreURL);
else
@@ -717,45 +706,53 @@ PopupNotifications.prototype = {
popupnotification.setAttribute("origin", uri);
} catch (e) {
Cu.reportError(e);
popupnotification.removeAttribute("origin");
}
} else
popupnotification.removeAttribute("origin");
+ if (n.options.hideClose)
+ popupnotification.setAttribute("hideclose", "true");
+
popupnotification.notification = n;
- if (n.secondaryActions) {
+ if (n.secondaryActions && n.secondaryActions.length > 0) {
let telemetryStatId = TELEMETRY_STAT_ACTION_2;
- n.secondaryActions.forEach(function (a) {
+ let secondaryAction = n.secondaryActions[0];
+ popupnotification.setAttribute("secondarybuttonlabel", secondaryAction.label);
+ popupnotification.setAttribute("secondarybuttonaccesskey", secondaryAction.accessKey);
+ popupnotification.setAttribute("secondarybuttoncommand", "PopupNotifications._onButtonEvent(event, 'secondarybuttoncommand');");
+
+ for (let i = 1; i < n.secondaryActions.length; i++) {
+ let action = n.secondaryActions[i];
let item = doc.createElementNS(XUL_NS, "menuitem");
- item.setAttribute("label", a.label);
- item.setAttribute("accesskey", a.accessKey);
+ item.setAttribute("label", action.label);
+ item.setAttribute("accesskey", action.accessKey);
item.notification = n;
- item.action = a;
+ item.action = action;
popupnotification.appendChild(item);
// We can only record a limited number of actions in telemetry. If
// there are more, the latest are all recorded in the last bucket.
item.action.telemetryStatId = telemetryStatId;
if (telemetryStatId < TELEMETRY_STAT_ACTION_LAST) {
telemetryStatId++;
}
- }, this);
-
- if (n.options.hideNotNow) {
- popupnotification.setAttribute("hidenotnow", "true");
}
- else if (n.secondaryActions.length) {
- let closeItemSeparator = doc.createElementNS(XUL_NS, "menuseparator");
- popupnotification.appendChild(closeItemSeparator);
+
+ if (n.secondaryActions.length < 2) {
+ popupnotification.setAttribute("hidemenu", "true");
}
+ } else {
+ popupnotification.setAttribute("secondarybuttondisabled", "true");
+ popupnotification.setAttribute("hidemenu", "true");
}
let checkbox = n.options.checkbox;
if (checkbox && checkbox.label) {
let checked = n._checkboxChecked != null ? n._checkboxChecked : !!checkbox.checked;
popupnotification.setAttribute("checkboxhidden", "false");
popupnotification.setAttribute("checkboxchecked", checked);
@@ -776,22 +773,26 @@ PopupNotifications.prototype = {
// The popupnotification may be hidden if we got it from the chrome
// document rather than creating it ad hoc.
popupnotification.hidden = false;
}, this);
},
_setNotificationUIState(notification, state={}) {
- notification.setAttribute("mainactiondisabled", state.disableMainAction || "false");
-
- if (state.warningLabel) {
- notification.setAttribute("warninglabel", state.warningLabel);
- notification.setAttribute("warninghidden", "false");
+ if (state.disableMainAction) {
+ notification.setAttribute("mainactiondisabled", state.disableMainAction);
+ if (state.warningLabel) {
+ notification.setAttribute("warninglabel", state.warningLabel);
+ notification.removeAttribute("warninghidden");
+ } else {
+ notification.setAttribute("warninghidden", "true");
+ }
} else {
+ notification.removeAttribute("mainactiondisabled");
notification.setAttribute("warninghidden", "true");
}
},
_onCheckboxCommand(event) {
let notificationEl = getNotificationFromElement(event.originalTarget);
let checked = notificationEl.checkbox.checked;
let notification = notificationEl.notification;
@@ -1253,44 +1254,56 @@ PopupNotifications.prototype = {
return;
}
if (type == "learnmoreclick") {
notification._recordTelemetryStat(TELEMETRY_STAT_LEARN_MORE);
return;
}
- // Record the total timing of the main action since the notification was
- // created, even if the notification was dismissed in the meantime.
- let timeSinceCreated = this.window.performance.now() - notification.timeCreated;
- if (!notification.recordedTelemetryMainAction) {
- notification.recordedTelemetryMainAction = true;
- notification._recordTelemetry("POPUP_NOTIFICATION_MAIN_ACTION_MS",
- timeSinceCreated);
+ if (type == "buttoncommand") {
+ // Record the total timing of the main action since the notification was
+ // created, even if the notification was dismissed in the meantime.
+ let timeSinceCreated = this.window.performance.now() - notification.timeCreated;
+ if (!notification.recordedTelemetryMainAction) {
+ notification.recordedTelemetryMainAction = true;
+ notification._recordTelemetry("POPUP_NOTIFICATION_MAIN_ACTION_MS",
+ timeSinceCreated);
+ }
}
- let timeSinceShown = this.window.performance.now() - notification.timeShown;
- if (timeSinceShown < this.buttonDelay) {
- Services.console.logStringMessage("PopupNotifications._onButtonEvent: " +
- "Button click happened before the security delay: " +
- timeSinceShown + "ms");
- return;
+ if (type == "buttoncommand" || type == "secondarybuttoncommand") {
+ let timeSinceShown = this.window.performance.now() - notification.timeShown;
+ if (timeSinceShown < this.buttonDelay) {
+ Services.console.logStringMessage("PopupNotifications._onButtonEvent: " +
+ "Button click happened before the security delay: " +
+ timeSinceShown + "ms");
+ return;
+ }
}
- notification._recordTelemetryStat(TELEMETRY_STAT_ACTION_1);
+ let action = notification.mainAction;
+ let telemetryStatId = TELEMETRY_STAT_ACTION_1;
+
+ if (type == "secondarybuttoncommand") {
+ action = notification.secondaryActions[0];
+ telemetryStatId = TELEMETRY_STAT_ACTION_2;
+ }
+
+ notification._recordTelemetryStat(telemetryStatId);
try {
- notification.mainAction.callback.call(undefined, {
+ action.callback.call(undefined, {
checkboxChecked: notificationEl.checkbox.checked
});
} catch (error) {
Cu.reportError(error);
}
- if (notification.mainAction.dismiss) {
+ if (action.dismiss) {
this._dismiss();
return;
}
this._remove(notification);
this._update();
},
--- a/toolkit/themes/osx/global/notification.css
+++ b/toolkit/themes/osx/global/notification.css
@@ -105,16 +105,11 @@ notification[type="info"]:not([value="tr
%include ../../shared/popupnotification.inc.css
.popup-notification-button:focus {
outline: 2px -moz-mac-focusring solid;
outline-offset: -2px;
}
-/* This is changed due to hover transparency looking invisible when buttons are hovered */
-.popup-notification-button-wrapper {
- --panel-separator-color: white;
-}
-
.popup-notification-warning {
color: #aa1b08;
}
deleted file mode 100644
--- a/toolkit/themes/shared/icons/menubutton-dropmarker-white.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg"
- width="16" height="16" viewBox="0 0 16 16">
- <path fill="#fff" d="m 2,6 6,6 6,-6 -1.5,-1.5 -4.5,4.5 -4.5,-4.5 z" />
-</svg>
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -22,17 +22,16 @@ toolkit.jar:
skin/classic/global/appPicker.css (../../shared/appPicker.css)
skin/classic/global/config.css (../../shared/config.css)
skin/classic/global/timepicker.css (../../shared/timepicker.css)
skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg)
skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg)
skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg)
skin/classic/global/icons/loading.png (../../shared/icons/loading.png)
skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png)
- skin/classic/global/icons/menubutton-dropmarker-white.svg (../../shared/icons/menubutton-dropmarker-white.svg)
skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg)
skin/classic/global/icons/blocked.svg (../../shared/incontent-icons/blocked.svg)
skin/classic/global/alerts/alert-common.css (../../shared/alert-common.css)
skin/classic/global/narrate.css (../../shared/narrate.css)
skin/classic/global/narrateControls.css (../../shared/narrateControls.css)
skin/classic/global/narrate/arrow.svg (../../shared/narrate/arrow.svg)
skin/classic/global/narrate/back.svg (../../shared/narrate/back.svg)
skin/classic/global/narrate/fast.svg (../../shared/narrate/fast.svg)
--- a/toolkit/themes/shared/popupnotification.inc.css
+++ b/toolkit/themes/shared/popupnotification.inc.css
@@ -1,24 +1,22 @@
/* 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/. */
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-
/* Popup notification */
.popup-notification-body-container {
- padding: var(--panel-arrowcontent-padding);
+ padding: var(--arrowpanel-padding);
}
.popup-notification-icon {
width: 32px;
height: 32px;
- margin-inline-end: var(--panel-arrowcontent-padding);
+ margin-inline-end: var(--arrowpanel-padding);
}
.popup-notification-body {
width: 25em;
}
.popup-notification-body description,
.popup-notification-body label {
@@ -40,61 +38,62 @@
margin-bottom: .3em !important;
}
.popup-notification-learnmore-link {
margin-top: .5em !important;
}
.popup-notification-button-container {
- background-color: hsla(210,4%,10%,.07);
+ background-color: var(--arrowpanel-dimmed);
border-top: 1px solid var(--panel-separator-color);
+ display: flex;
}
-.popup-notification-button-wrapper {
- background-color: #0996f8;
-}
-
-.popup-notification-button-wrapper > toolbarseparator {
+.popup-notification-button-container > toolbarseparator {
border: 0;
border-left: 1px solid var(--panel-separator-color);
margin: 7px 0 7px;
/* This is to override the linux !important */
-moz-appearance: none !important;
min-width: 0;
}
-.popup-notification-button-wrapper:hover > toolbarseparator {
+.popup-notification-button-container:hover > toolbarseparator {
margin: 0;
}
.popup-notification-button {
-moz-appearance: none;
- background-color: transparent;
+ background-color: #0996f8;
color: white;
margin: 0;
padding: 0 18px;
min-height: 40px;
- min-width: 0;
+ min-width: 50%;
+ max-width: 50%;
border: none;
+ flex: auto;
}
-.popup-notification-button:hover {
- outline: 1px solid hsla(210,4%,10%,.07);
+.popup-notification-button:hover:not([disabled]) {
+ outline: 1px solid var(--arrowpanel-dimmed);
background-color: #0675d3;
}
-.popup-notification-button:hover:active {
- outline: 1px solid hsla(210,4%,10%,.12);
+.popup-notification-button:hover:active:not([disabled]) {
+ outline: 1px solid var(--arrowpanel-dimmed-further);
background-color: #0568ba;
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}
.popup-notification-dropmarker {
- padding: 0 15px;
+ padding: 0;
+ min-width: 0;
+ flex: none;
}
/* prevent double border on windows when focused */
.popup-notification-button > .button-box {
border: none;
}
.popup-notification-dropmarker > .button-box > hbox {
@@ -105,14 +104,37 @@
/* This is to override the linux !important */
-moz-appearance: none !important;
display: -moz-box;
}
.popup-notification-dropmarker > .button-box > .button-menu-dropmarker > .dropmarker-icon {
width: 16px;
height: 16px;
- list-style-image: url(chrome://global/skin/icons/menubutton-dropmarker-white.svg);
+ /* XXX: move the following svg files to toolkit? */
+ list-style-image: url(chrome://browser/skin/downloads/menubutton-dropmarker.svg);
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: #666;
}
.popup-notification-button[disabled] {
- opacity: 0.5;
+ opacity: 0.3;
+ background-color: var(--arrowpanel-dimmed-further);
+ color: inherit;
+}
+
+.popup-notification-button-secondary {
+ background-color: transparent;
+ color: inherit;
+ min-width: 0;
}
+
+.popup-notification-button-secondary[disabled] {
+ visibility: hidden;
+}
+
+.popup-notification-button-secondary:hover:not([disabled]) {
+ background-color: var(--arrowpanel-dimmed);
+}
+
+.popup-notification-button-secondary:hover:active:not([disabled]) {
+ background-color: var(--arrowpanel-dimmed-further);
+}