Bug 1282768 - Part 3 - Convert permission notifications to use two buttons and a checkbox instead of multiple secondary actions. r=florian draft
authorPanos Astithas <past@mozilla.com>
Sun, 20 Nov 2016 18:40:26 +0100
changeset 441675 daf79cf7ab46e2605c1c86c0d2f0f6c9cd8c972e
parent 441674 84b7ab2a16141c736dc0f6b84387fb1be4775b59
child 441676 d7089c38a63e2c53f6d31b72ea17a93e0372fc38
push id36489
push userpaolo.mozmail@amadzone.org
push dateSun, 20 Nov 2016 17:49:09 +0000
reviewersflorian
bugs1282768
milestone53.0a1
Bug 1282768 - Part 3 - Convert permission notifications to use two buttons and a checkbox instead of multiple secondary actions. r=florian MozReview-Commit-ID: KNf04CBaLKB
browser/base/content/browser.js
browser/base/content/popup-notifications.inc
browser/base/content/test/popupNotifications/browser_popupNotification.js
browser/base/content/test/webrtc/browser_devices_get_user_media.js
browser/base/content/test/webrtc/head.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/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/locales/en-US/chrome/passwordmgr/passwordmgr.properties
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5965,18 +5965,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
@@ -6025,38 +6029,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.label"),
+        accessKey: gNavigatorBundle.getString("offlineApps.allowStoring.accesskey"),
         callback: function() {
           for (let [ciBrowser, ciDocId, ciUri] of notification.options.controlledItems) {
             OfflineApps.allowSite(ciBrowser, ciDocId, ciUri);
           }
         }
       };
       let secondaryActions = [{
-        label: gNavigatorBundle.getString("offlineApps.never"),
-        accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+        label: gNavigatorBundle.getString("offlineApps.dontAllow.label"),
+        accessKey: gNavigatorBundle.getString("offlineApps.dontAllow.accesskey"),
         callback: function() {
           for (let [, , ciUri] of notification.options.controlledItems) {
             OfflineApps.disallowSite(ciUri);
           }
         }
       }];
-      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);
     }
   },
 
@@ -6128,51 +6133,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.label"),
+      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.label"),
+        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/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -17,17 +17,17 @@
           <menupopup id="webRTC-selectCamera-menupopup"/>
         </menulist>
       </popupnotificationcontent>
 
       <popupnotificationcontent id="webRTC-selectWindowOrScreen" orient="vertical">
         <label id="webRTC-selectWindow-label"
                control="webRTC-selectWindow-menulist"/>
         <menulist id="webRTC-selectWindow-menulist"
-                  oncommand="webrtcUI.updateMainActionLabel(this);">
+                  oncommand="webrtcUI.updateWarningLabel(this);">
           <menupopup id="webRTC-selectWindow-menupopup"/>
         </menulist>
         <description id="webRTC-all-windows-shared" hidden="true">&getUserMedia.allWindowsShared.message;</description>
       </popupnotificationcontent>
 
       <popupnotificationcontent id="webRTC-preview" hidden="true">
         <html:video id="webRTC-previewVideo"/>
         <vbox id="webRTC-previewWarningBox">
--- a/browser/base/content/test/popupNotifications/browser_popupNotification.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification.js
@@ -43,54 +43,54 @@ var tests = [
     },
     onHidden: function(popup) {
       ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
     }
   },
   { id: "Test#2b",
-    run: function () {
+    run: function() {
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.secondaryActions.push({
         label: "Extra Secondary Action",
         accessKey: "E",
         callback: () => this.extraSecondaryActionClicked = true,
       });
       showNotification(this.notifyObj);
     },
-    onShown: function (popup) {
+    onShown: function(popup) {
       checkPopup(popup, this.notifyObj);
       triggerSecondaryCommand(popup, 1);
     },
-    onHidden: function (popup) {
+    onHidden: function(popup) {
       ok(this.extraSecondaryActionClicked, "extra secondary action was clicked");
       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
     }
   },
   { id: "Test#2c",
-    run: function () {
+    run: function() {
       this.notifyObj = new BasicNotification(this.id);
       this.notifyObj.secondaryActions.push({
         label: "Extra Secondary Action",
         accessKey: "E",
         callback: () => ok(false, "unexpected callback invocation"),
       }, {
         label: "Other Extra Secondary Action",
         accessKey: "O",
         callback: () => this.extraSecondaryActionClicked = true,
       });
       showNotification(this.notifyObj);
     },
-    onShown: function (popup) {
+    onShown: function(popup) {
       checkPopup(popup, this.notifyObj);
       triggerSecondaryCommand(popup, 2);
     },
-    onHidden: function (popup) {
+    onHidden: function(popup) {
       ok(this.extraSecondaryActionClicked, "extra secondary action was clicked");
       ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
       ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
     }
   },
   { id: "Test#3",
     run: function() {
       this.notifyObj = new BasicNotification(this.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
@@ -469,17 +469,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;
@@ -489,26 +489,26 @@ 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");
+    ok(!notification.hasAttribute("warninghidden"), "warning message is shown");
 
     // 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,38 +300,27 @@ function promiseNoPopupNotification(aNam
 }
 
 const kActionAlways = 1;
 const kActionDeny = 2;
 const kActionNever = 3;
 
 function activateSecondaryAction(aAction) {
   let notification = PopupNotifications.panel.firstChild;
-
-  if (aAction == kActionAlways) {
-    notification.secondaryButton.click();
-    return;
+  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;
   }
-
-  notification.secondaryButton.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") });
 }
 
 function getMediaCaptureState() {
   return new Promise(resolve => {
     let mm = _mm();
     mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
       resolve(data);
     });
--- 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.label=Allow Storing Data
+offlineApps.allowStoring.accesskey=A
+offlineApps.dontAllow.label=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 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 listen to this tab’s audio?
 # LOCALIZATION NOTE (getUserMedia.shareScreenWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
 # %S will be the 'learn more' link
 getUserMedia.shareScreenWarning.message = Only share screens with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %S
 # LOCALIZATION NOTE (getUserMedia.shareFirefoxWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
 # %1$S is brandShortName (eg. Firefox)
 # %2$S will be the 'learn more' link
 getUserMedia.shareFirefoxWarning.message = Only share %1$S with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %2$S
 # LOCALIZATION NOTE(getUserMedia.shareScreen.learnMoreLabel): NB: inserted via innerHTML, so please don't use <, > or & in this string.
@@ -510,32 +505,28 @@ 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.allow.label = Allow
+getUserMedia.allow.accesskey = A
+getUserMedia.dontAllow.label = Don’t Allow
+getUserMedia.dontAllow.accesskey = D
+getUserMedia.remember=Remember this decision
+# LOCALIZATION NOTE (ggetUserMedia.reasonForNoPermanentAllow.screen,
+#                    getUserMedia.reasonForNoPermanentAllow.audio,
+#                    getUserMedia.reasonForNoPermanentAllow.insecure):
+# %S is brandShortName
+getUserMedia.reasonForNoPermanentAllow.screen=%S can not allow permanent access to your screen or application without asking which one to share.
+getUserMedia.reasonForNoPermanentAllow.audio=%S can not allow permanent access to your tab’s audio without asking which tab to share.
+getUserMedia.reasonForNoPermanentAllow.insecure=Your connection to this site is not secure. To protect you, %S will only allow access for this session.
 
 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 ||
@@ -332,17 +335,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,
@@ -409,89 +414,99 @@ 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 hostPort = "<>";
+      try {
+        hostPort = this.principal.URI.hostPort;
+      } catch (ex) { }
+      message = gBrowserBundle.formatStringFromName("geolocation.shareWithSite3",
+                                                    [hostPort], 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);
   },
 };
@@ -516,81 +531,71 @@ 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 hostPort = "<>";
+    try {
+      hostPort = this.principal.URI.hostPort;
+    } catch (ex) { }
+    return gBrowserBundle.formatStringFromName("webNotifications.receiveFromSite2",
+                                               [hostPort], 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
@@ -247,16 +247,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);
@@ -150,30 +154,20 @@ this.webrtcUI = {
       });
       Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport)
         .activateApplication(true);
       return;
     }
     identityBox.click();
   },
 
-  updateMainActionLabel: function(aMenuList) {
+  updateWarningLabel: function(aMenuList) {
     let type = aMenuList.selectedItem.getAttribute("devicetype");
     let document = aMenuList.ownerDocument;
     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 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:
       //
       //   var stockReceiveMessage = webrtcUI.receiveMessage;
@@ -289,79 +283,71 @@ function getHost(uri, href) {
 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 stringBundle = chromeDoc.defaultView.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.allow.label"),
+    accessKey: stringBundle.getString("getUserMedia.allow.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);
-      }
-    });
-  }
+
+  let productName = gBrandBundle.GetStringFromName("brandShortName");
 
-  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);
-      }
-    });
+  // Disable the permanent '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).
+  let reasonForNoPermanentAllow = "";
+  if (sharingScreen) {
+    reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.screen";
+  } else if (sharingAudio) {
+    reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.audio";
+  } else if (!aRequest.secure) {
+    reasonForNoPermanentAllow = "getUserMedia.reasonForNoPermanentAllow.insecure";
   }
 
   let options = {
     persistent: true,
+    hideClose: true,
+    checkbox: {
+      label: stringBundle.getString("getUserMedia.remember"),
+      checkedState: reasonForNoPermanentAllow ? {
+        disableMainAction: true,
+        warningLabel: stringBundle.getFormattedString(reasonForNoPermanentAllow,
+                                                      [productName])
+      } : undefined,
+    },
     eventCallback: function(aTopic, aNewBrowser) {
       if (aTopic == "swapping")
         return true;
 
       let doc = this.browser.ownerDocument;
 
       // Clean-up video streams of screensharing previews.
       if ((aTopic == "dismissed" || aTopic == "removed") &&
@@ -599,57 +585,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 = doc.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 = doc.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/notification/test/browser/browser_permission_dismiss.js
+++ b/dom/notification/test/browser/browser_permission_dismiss.js
@@ -91,21 +91,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("notifyBarNeverRememberButtonAccessKey2"),
+        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,19 +1035,19 @@ 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");
+          this._getLocalizedString("notifyBarNeverRememberButtonAccessKey2");
     var rememberButtonText =
           this._getLocalizedString("notifyBarRememberPasswordButtonText");
     var rememberButtonAccessKey =
           this._getLocalizedString("notifyBarRememberPasswordButtonAccessKey");
 
     var displayHost = this._getShortDisplayHost(aLogin.hostname);
     var notificationText = this._getLocalizedString(
                                   "rememberPasswordMsgNoUsername",
--- 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
@@ -68,17 +68,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.
  *
@@ -89,17 +89,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.
  *
--- a/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
+++ b/toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties
@@ -1,42 +1,47 @@
 # 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 (saveLoginMsg, saveLoginMsgNoUser):
+# %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
-notifyBarNeverRememberButtonAccessKey = e
+notifyBarNeverRememberButtonText2 = Never Save
+notifyBarNeverRememberButtonAccessKey2 = 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”?
 updatePasswordMsgNoUser = Would you like to update the saved password?