Bug 1282768 - Part 3 - Convert permission notifications to use two buttons and a checkbox instead of multiple secondary actions. r=florian
authorPanos Astithas <past@mozilla.com>
Sun, 20 Nov 2016 18:40:26 +0100
changeset 323542 d229169c60eaf88f7f2531643104e264561b2ea1
parent 323541 3304424abe70d6ab310c71e2f6ca8b264b286134
child 323543 53fc06a96f27f5a9e9b8870831afaabf5166cbde
push id30978
push usercbook@mozilla.com
push dateMon, 21 Nov 2016 14:44:46 +0000
treeherdermozilla-central@0534254e9a40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersflorian
bugs1282768
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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?