Bug 1282768 - Convert permission notifications to use two buttons instead of the drop-down menu. r=florian,paolo draft
authorPanos Astithas <past@mozilla.com>
Thu, 18 Aug 2016 01:05:41 +0300
changeset 432337 24623a94f8e903f65dfae35a0cf693a3a9824230
parent 432336 1b1754ba09dc3791de64433c37c0a18cc3e05b3a
child 535608 9f1bb91a476dfd910f72eb84ca266a4738381970
push id34266
push userbmo:past@mozilla.com
push dateTue, 01 Nov 2016 17:42:29 +0000
reviewersflorian, paolo
bugs1282768
milestone52.0a1
Bug 1282768 - Convert permission notifications to use two buttons instead of the drop-down menu. r=florian,paolo MozReview-Commit-ID: 2cnufF7wZJG
browser/base/content/browser.js
browser/base/content/test/popupNotifications/browser_popupNotification_2.js
browser/base/content/test/popupNotifications/browser_popupNotification_4.js
browser/base/content/test/popupNotifications/browser_popupNotification_5.js
browser/base/content/test/popupNotifications/head.js
browser/base/content/test/webrtc/browser_devices_get_user_media.js
browser/base/content/test/webrtc/head.js
browser/components/nsBrowserGlue.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/PermissionUI.jsm
browser/modules/test/browser_PermissionUI.js
browser/modules/webrtcUI.jsm
dom/indexedDB/test/head.js
dom/notification/test/browser/browser_permission_dismiss.js
toolkit/components/passwordmgr/nsLoginManagerPrompter.js
toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
toolkit/components/passwordmgr/test/browser/head.js
toolkit/content/widgets/notification.xml
toolkit/locales/en-US/chrome/global/notification.dtd
toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
toolkit/modules/PopupNotifications.jsm
toolkit/themes/osx/global/notification.css
toolkit/themes/shared/icons/menubutton-dropmarker-white.svg
toolkit/themes/shared/jar.inc.mn
toolkit/themes/shared/popupnotification.inc.css
--- 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);
+}