Bug 1088220 - Add login doorhanger. r=margaret
authorChenxia Liu <liuche@mozilla.com>
Thu, 26 Mar 2015 16:34:29 -0700
changeset 266253 ebf592c2fa4c57503dd4498006e20162a5133a24
parent 266252 e734718125b0f7aceeae8c1d17acb78887ce3308
child 266254 28bc7e898980ff509f986ebb8a76df6b41f55914
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs1088220
milestone39.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 1088220 - Add login doorhanger. r=margaret
mobile/android/base/DoorHangerPopup.java
mobile/android/base/moz.build
mobile/android/base/resources/layout/doorhanger.xml
mobile/android/base/resources/layout/login_doorhanger.xml
mobile/android/base/resources/values/dimens.xml
mobile/android/base/resources/values/styles.xml
mobile/android/base/widget/DoorHanger.java
mobile/android/base/widget/LoginDoorHanger.java
mobile/android/chrome/content/browser.js
mobile/android/components/LoginManagerPrompter.js
mobile/locales/en-US/overrides/passwordmgr.properties
--- a/mobile/android/base/DoorHangerPopup.java
+++ b/mobile/android/base/DoorHangerPopup.java
@@ -110,18 +110,18 @@ public class DoorHangerPopup extends Anc
         final int tabId = json.getInt("tabID");
         final String id = json.getString("value");
         final DoorhangerConfig config = new DoorhangerConfig(tabId, id);
 
         config.setMessage(json.getString("message"));
         config.setButtons(json.getJSONArray("buttons"));
         config.setOptions(json.getJSONObject("options"));
         final String typeString = json.optString("category");
-        if (DoorHanger.Type.PASSWORD.toString().equals(typeString)) {
-            config.setType(DoorHanger.Type.PASSWORD);
+        if (DoorHanger.Type.LOGIN.toString().equals(typeString)) {
+            config.setType(DoorHanger.Type.LOGIN);
         }
 
         return config;
     }
 
     // This callback is automatically executed on the UI thread.
     @Override
     public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) {
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -500,16 +500,17 @@ gbjar.sources += [
     'widget/FaviconView.java',
     'widget/FloatingHintEditText.java',
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
     'widget/GeckoSwipeRefreshLayout.java',
     'widget/GeckoViewFlipper.java',
     'widget/IconTabWidget.java',
+    'widget/LoginDoorHanger.java',
     'widget/ResizablePathDrawable.java',
     'widget/SquaredImageView.java',
     'widget/SwipeDismissListViewTouchListener.java',
     'widget/TabThumbnailWrapper.java',
     'widget/ThumbnailView.java',
     'widget/TwoWayView.java',
     'ZoomConstraints.java',
     'ZoomedView.java',
--- a/mobile/android/base/resources/layout/doorhanger.xml
+++ b/mobile/android/base/resources/layout/doorhanger.xml
@@ -15,17 +15,17 @@
                    android:layout_height="wrap_content"
                    android:paddingRight="@dimen/doorhanger_padding"
                    android:visibility="gone"/>
 
         <TextView android:id="@+id/doorhanger_message"
                   android:focusable="true"
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"
-                  android:textAppearance="@style/TextAppearance.Widget.DoorHanger.Medium"/>
+                  android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
 
     </LinearLayout>
 
     <LinearLayout android:id="@+id/doorhanger_inputs"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:orientation="vertical"
               android:gravity="right"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/login_doorhanger.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:orientation="horizontal"
+                  android:paddingTop="@dimen/doorhanger_section_padding_small"
+                  android:paddingLeft="@dimen/doorhanger_section_padding_small">
+
+        <ImageView android:id="@+id/doorhanger_icon"
+                   android:layout_width="@dimen/doorhanger_icon_size"
+                   android:layout_height="@dimen/doorhanger_icon_size"
+                   android:layout_gravity="center_horizontal"
+                   android:paddingRight="@dimen/doorhanger_section_padding_small"
+                   android:src="@drawable/icon_key"/>
+
+        <LinearLayout android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:orientation="vertical">
+
+            <TextView android:id="@+id/doorhanger_title"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:paddingBottom="@dimen/doorhanger_section_padding_small"
+                      android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/>
+
+            <TextView android:id="@+id/doorhanger_message"
+                      android:focusable="true"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:paddingBottom="@dimen/doorhanger_section_padding_large"
+                      android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
+
+            <TextView android:id="@+id/doorhanger_login"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
+                      android:paddingBottom="@dimen/doorhanger_section_padding_large"
+                      android:visibility="gone"/>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <View android:id="@+id/divider_buttons"
+          android:layout_width="match_parent"
+          android:layout_height="1dp"
+          android:background="@color/divider_light"
+          android:visibility="gone"/>
+
+    <LinearLayout android:id="@+id/doorhanger_buttons"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:orientation="horizontal"
+                  android:visibility="gone"/>
+
+   <View android:id="@+id/divider_doorhanger"
+          android:layout_width="match_parent"
+          android:layout_height="1dp"
+          android:background="@color/divider_light"
+          android:visibility="gone"/>
+
+</merge>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -95,16 +95,20 @@
     <dimen name="identity_padding_top">5dp</dimen>
 
     <dimen name="doorhanger_width">300dp</dimen>
     <dimen name="doorhanger_input_width">250dp</dimen>
     <dimen name="doorhanger_spinner_textsize">9sp</dimen>
     <dimen name="doorhanger_padding">15dp</dimen>
     <dimen name="doorhanger_offsetX">10dp</dimen>
     <dimen name="doorhanger_offsetY">7dp</dimen>
+    <dimen name="doorhanger_drawable_padding">5dp</dimen>
+    <dimen name="doorhanger_section_padding_small">20dp</dimen>
+    <dimen name="doorhanger_section_padding_large">30dp</dimen>
+    <dimen name="doorhanger_icon_size">60dp</dimen>
 
     <dimen name="flow_layout_spacing">6dp</dimen>
     <dimen name="menu_item_icon">21dp</dimen>
     <dimen name="menu_item_textsize">16sp</dimen>
     <dimen name="menu_item_state_icon">18dp</dimen>
     <!-- This is chosen to match Android's listPreferredItemHeight.
          TODO: We should inherit these from the system.
          http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/res/res/values/themes.xml#123 -->
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -405,24 +405,31 @@
     <style name="TextAppearance.Widget.Home.ItemDescription" parent="TextAppearance.Micro">
         <item name="android:textColor">#AFB1B3</item>
     </style>
 
     <style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorHint</item>
     </style>
 
-    <style name="TextAppearance.Widget.DoorHanger.Medium" parent="TextAppearance.Medium">
+    <style name="TextAppearance.DoorHanger">
         <item name="android:textColor">@color/placeholder_active_grey</item>
         <item name="android:textColorLink">@color/doorhanger_link</item>
     </style>
 
-    <style name="TextAppearance.Widget.DoorHanger.Small" parent="TextAppearance.Small">
-        <item name="android:textColor">@color/placeholder_active_grey</item>
-        <item name="android:textColorLink">@color/doorhanger_link</item>
+    <style name="TextAppearance.DoorHanger.Medium">
+        <item name="android:textSize">16dp</item>
+    </style>
+
+    <style name="TextAppearance.DoorHanger.Medium.Light">
+        <item name="android:fontFamily">sans-serif-light</item>
+    </style>
+
+    <style name="TextAppearance.DoorHanger.Small">
+        <item name="android:textSize">14sp</item>
     </style>
 
     <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Small">
         <item name="android:textSize">15sp</item>
     </style>
 
     <!-- BrowserToolbar -->
     <style name="BrowserToolbar">
--- a/mobile/android/base/widget/DoorHanger.java
+++ b/mobile/android/base/widget/DoorHanger.java
@@ -27,26 +27,27 @@ import org.mozilla.gecko.prompts.PromptI
 import java.util.List;
 
 public abstract class DoorHanger extends LinearLayout {
 
     public static DoorHanger Get(Context context, DoorhangerConfig config) {
         final Type type = config.getType();
         if (type != null) {
             switch (type) {
-                case PASSWORD:
+                case LOGIN:
+                    return new LoginDoorHanger(context, config);
                 case SITE:
                     return new DefaultDoorHanger(context, config, type);
             }
         }
 
         return new DefaultDoorHanger(context, config);
     }
 
-    public static enum Type { DEFAULT, PASSWORD, SITE }
+    public static enum Type { DEFAULT, LOGIN, SITE }
 
     public interface OnButtonClickListener {
         public void onButtonClick(DoorHanger dh, String tag);
     }
 
     private static final LayoutParams sButtonParams;
     static {
         sButtonParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f);
@@ -79,30 +80,29 @@ public abstract class DoorHanger extends
     protected DoorHanger(Context context, DoorhangerConfig config, Type type) {
         super(context);
         mContext = context;
         mTabId = config.getTabId();
         mIdentifier = config.getId();
 
         int resource;
         switch (type) {
-            case PASSWORD:
-                // TODO: switch to R.layout.password
-                resource = R.layout.doorhanger;
+            case LOGIN:
+                resource = R.layout.login_doorhanger;
                 break;
             default:
                 resource = R.layout.doorhanger;
         }
 
         LayoutInflater.from(context).inflate(resource, this);
         mDivider = findViewById(R.id.divider_doorhanger);
         mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
         mMessage = (TextView) findViewById(R.id.doorhanger_message);
         if (type == Type.SITE) {
-            mMessage.setTextAppearance(getContext(), R.style.TextAppearance_Widget_DoorHanger_Small);
+            mMessage.setTextAppearance(getContext(), R.style.TextAppearance_DoorHanger_Small);
         }
         mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
 
         mDividerColor = getResources().getColor(R.color.divider_light);
         setOrientation(VERTICAL);
     }
 
     abstract protected void loadConfig(DoorhangerConfig config);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/LoginDoorHanger.java
@@ -0,0 +1,79 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+import ch.boye.httpclientandroidlib.util.TextUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.favicons.Favicons;
+import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
+
+public class LoginDoorHanger extends DoorHanger {
+    private static final String LOGTAG = "LoginDoorHanger";
+
+    final TextView mTitle;
+    final TextView mLogin;
+
+    public LoginDoorHanger(Context context, DoorhangerConfig config) {
+        super(context, config, Type.LOGIN);
+
+        mTitle = (TextView) findViewById(R.id.doorhanger_title);
+        mLogin = (TextView) findViewById(R.id.doorhanger_login);
+
+        loadConfig(config);
+    }
+
+    @Override
+    protected void loadConfig(DoorhangerConfig config) {
+        setOptions(config.getOptions());
+        setMessage(config.getMessage());
+
+    }
+
+    @Override
+    protected void setOptions(final JSONObject options) {
+        super.setOptions(options);
+
+        final JSONObject titleObj = options.optJSONObject("title");
+        if (titleObj != null) {
+
+            try {
+                final String text = titleObj.getString("text");
+                mTitle.setText(text);
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "Error loading title from options JSON");
+            }
+
+            final String resource = titleObj.optString("resource");
+            if (resource != null) {
+                Favicons.getSizedFaviconForPageFromLocal(mContext, resource, 32, new OnFaviconLoadedListener() {
+                    @Override
+                    public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
+                        if (favicon != null) {
+                            mTitle.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(mContext.getResources(), favicon), null, null, null);
+                            mTitle.setCompoundDrawablePadding((int) mContext.getResources().getDimension(R.dimen.doorhanger_drawable_padding));
+                        }
+                    }
+                });
+            }
+        }
+
+        final String subtext = options.optString("subtext");
+        if (!TextUtils.isEmpty(subtext)) {
+            mLogin.setText(subtext);
+            mLogin.setVisibility(View.VISIBLE);
+        } else {
+            mLogin.setVisibility(View.GONE);
+        }
+    }
+}
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2208,21 +2208,34 @@ var NativeWindow = {
    *        persistence: An integer. The notification will not automatically
    *                     dismiss for this many page loads. If persistence is set
    *                     to -1, the doorhanger will never automatically dismiss.
    *        persistWhileVisible:
    *                     A boolean. If true, a visible notification will always
    *                     persist across location changes.
    *        timeout:     A time in milliseconds. The notification will not
    *                     automatically dismiss before this time.
+   *
    *        checkbox:    A string to appear next to a checkbox under the notification
    *                     message. The button callback functions will be called with
    *                     the checked state as an argument.                   
+   *
+   *        title:       An object that specifies text to display as the title, and
+   *                     optionally a resource, such as a favicon cache url that can be
+   *                     used to fetch a favicon from the FaviconCache. (This can be
+   *                     generalized to other resources if the situation arises.)
+   *                     { text: <title>,
+   *                       resource: <resource_url> }
+   *
+   *        subtext:     A string to appear below the doorhanger message.
+   *
+   * @param aCategory
+   *        Doorhanger type to display (e.g., LOGIN)
    */
-    show: function(aMessage, aValue, aButtons, aTabID, aOptions) {
+    show: function(aMessage, aValue, aButtons, aTabID, aOptions, aCategory) {
       if (aButtons == null) {
         aButtons = [];
       }
 
       aButtons.forEach((function(aButton) {
         this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId };
         aButton.callback = this._callbacksId;
         this._callbacksId++;
@@ -2231,17 +2244,18 @@ var NativeWindow = {
       this._promptId++;
       let json = {
         type: "Doorhanger:Add",
         message: aMessage,
         value: aValue,
         buttons: aButtons,
         // use the current tab if none is provided
         tabID: aTabID || BrowserApp.selectedTab.id,
-        options: aOptions || {}
+        options: aOptions || {},
+        category: aCategory
       };
       Messaging.sendRequest(json);
     },
 
     hide: function(aValue, aTabID) {
       Messaging.sendRequest({
         type: "Doorhanger:Remove",
         value: aValue,
--- a/mobile/android/components/LoginManagerPrompter.js
+++ b/mobile/android/components/LoginManagerPrompter.js
@@ -58,18 +58,22 @@ LoginManagerPrompter.prototype = {
         return this.__promptService;
     },
     
     __strBundle : null, // String bundle for L10N
     get _strBundle() {
         if (!this.__strBundle) {
             var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
                              getService(Ci.nsIStringBundleService);
-            this.__strBundle = bunService.createBundle(
-                        "chrome://passwordmgr/locale/passwordmgr.properties");
+            this.__strBundle = {
+              pwmgr : bunService.createBundle(
+                        "chrome://passwordmgr/locale/passwordmgr.properties"),
+              brand : bunService.createBundle("chrome://branding/locale/brand.properties")
+            };
+
             if (!this.__strBundle)
                 throw "String bundle for Login Manager not present!";
         }
 
         return this.__strBundle;
     },
 
 
@@ -131,86 +135,99 @@ LoginManagerPrompter.prototype = {
         Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED);
     },
 
 
     /*
      * _showLoginNotification
      *
      * Displays a notification doorhanger.
+     * @param aName
+     *        Name of notification
+     * @param aTitle
+     *        Object with title and optional resource to display with the title, such as a favicon key
+     * @param aBody
+     *        String message to be displayed in the doorhanger
+     * @param aButtons
+     *        Buttons to display with the doorhanger
+     * @param aSubtext
+     *        String to be displayed below the aBody message
      *
      */
-    _showLoginNotification : function (aName, aText, aButtons) {
+    _showLoginNotification : function (aName, aTitle, aBody, aButtons, aSubtext) {
         this.log("Adding new " + aName + " notification bar");
         let notifyWin = this._window.top;
         let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
         let browser = chromeWin.BrowserApp.getBrowserForWindow(notifyWin);
         let tabID = chromeWin.BrowserApp.getTabForBrowser(browser).id;
 
         // The page we're going to hasn't loaded yet, so we want to persist
         // across the first location change.
 
         // Sites like Gmail perform a funky redirect dance before you end up
         // at the post-authentication page. I don't see a good way to
         // heuristically determine when to ignore such location changes, so
         // we'll try ignoring location changes based on a time interval.
 
         let options = {
             persistWhileVisible: true,
-            timeout: Date.now() + 10000
+            timeout: Date.now() + 10000,
+            title: aTitle,
+            subtext: aSubtext
         }
 
         var nativeWindow = this._getNativeWindow();
         if (nativeWindow)
-            nativeWindow.doorhanger.show(aText, aName, aButtons, tabID, options);
+            nativeWindow.doorhanger.show(aBody, aName, aButtons, tabID, options, "LOGIN");
     },
 
 
     /*
      * _showSaveLoginNotification
      *
      * Displays a notification doorhanger (rather than a popup), to allow the user to
      * save the specified login. This allows the user to see the results of
      * their login, and only save a login which they know worked.
      *
      */
     _showSaveLoginNotification : function (aLogin) {
-        var displayHost = this._getShortDisplayHost(aLogin.hostname);
-        var notificationText;
+        let brandShortName = this._strBundle.brand.GetStringFromName("brandShortName");
+        let notificationText  = this._getLocalizedString("saveLogin", [brandShortName]);
+
+        let displayHost = this._getShortDisplayHost(aLogin.hostname);
+        let title = { text: displayHost, resource: aLogin.hostname };
+        let subtext = null;
+
         if (aLogin.username) {
-            var displayUser = this._sanitizeUsername(aLogin.username);
-            notificationText  = this._getLocalizedString("savePassword", [displayUser, displayHost]);
-        } else {
-            notificationText  = this._getLocalizedString("savePasswordNoUser", [displayHost]);
+          subtext = this._sanitizeUsername(aLogin.username);
         }
-
         // The callbacks in |buttons| have a closure to access the variables
         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
         // without a getService() call.
         var pwmgr = this._pwmgr;
         let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION");
 
         var buttons = [
             {
-                label: this._getLocalizedString("saveButton"),
+                label: this._getLocalizedString("neverButton"),
+                callback: function() {
+                    promptHistogram.add(PROMPT_NEVER);
+                    pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
+                }
+            },
+            {
+                label: this._getLocalizedString("rememberButton"),
                 callback: function() {
                     pwmgr.addLogin(aLogin);
                     promptHistogram.add(PROMPT_ADD);
                 }
-            },
-            {
-                label: this._getLocalizedString("dontSaveButton"),
-                callback: function() {
-                    promptHistogram.add(PROMPT_NOTNOW);
-                    // Don't set a permanent exception
-                }
             }
         ];
 
-        this._showLoginNotification("password-save", notificationText, buttons);
+        this._showLoginNotification("password-save", title, notificationText, buttons, subtext);
     },
 
     /*
      * promptToChangePassword
      *
      * Called when we think we detect a password change for an existing
      * login, when the form being submitted contains multiple password
      * fields.
@@ -231,40 +248,43 @@ LoginManagerPrompter.prototype = {
         var notificationText;
         if (aOldLogin.username) {
             let displayUser = this._sanitizeUsername(aOldLogin.username);
             notificationText  = this._getLocalizedString("updatePassword", [displayUser]);
         } else {
             notificationText  = this._getLocalizedString("updatePasswordNoUser");
         }
 
+        let displayHost = this._getShortDisplayHost(aOldLogin.hostname);
+        let title = { text: displayHost, resource: aOldLogin.hostname };
+
         // The callbacks in |buttons| have a closure to access the variables
         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
         // without a getService() call.
         var self = this;
         let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION");
 
         var buttons = [
             {
+                label: this._getLocalizedString("dontUpdateButton"),
+                callback:  function() {
+                    promptHistogram.add(PROMPT_NOTNOW);
+                    // do nothing
+                }
+            },
+            {
                 label: this._getLocalizedString("updateButton"),
                 callback:  function() {
                     self._updateLogin(aOldLogin, aNewPassword);
                     promptHistogram.add(PROMPT_UPDATE);
                 }
-            },
-            {
-                label: this._getLocalizedString("dontUpdateButton"),
-                callback:  function() {
-                    promptHistogram.add(PROMPT_NOTNOW);
-                    // do nothing
-                }
             }
         ];
 
-        this._showLoginNotification("password-change", notificationText, buttons);
+        this._showLoginNotification("password-change", title, notificationText, buttons);
     },
 
 
     /*
      * promptToChangePasswordWithUsernames
      *
      * Called when we detect a password change in a form submission, but we
      * don't know which existing login (username) it's for. Asks the user
@@ -372,20 +392,20 @@ LoginManagerPrompter.prototype = {
      *   (etc)
      *
      * Returns the localized string for the specified key,
      * formatted if required.
      *
      */ 
     _getLocalizedString : function (key, formatArgs) {
         if (formatArgs)
-            return this._strBundle.formatStringFromName(
+            return this._strBundle.pwmgr.formatStringFromName(
                                         key, formatArgs, formatArgs.length);
         else
-            return this._strBundle.GetStringFromName(key);
+            return this._strBundle.pwmgr.GetStringFromName(key);
     },
 
 
     /*
      * _sanitizeUsername
      *
      * Sanitizes the specified username, by stripping quotes and truncating if
      * it's too long. This helps prevent an evil site from messing with the
--- a/mobile/locales/en-US/overrides/passwordmgr.properties
+++ b/mobile/locales/en-US/overrides/passwordmgr.properties
@@ -1,19 +1,16 @@
 # 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/.
 
-# 1st string is the username for the login, 2nd is the login's hostname.
-# Note that long usernames may be truncated.
-savePassword=Save password for "%1$S" on %2$S?
-# String is the login's hostname
-savePasswordNoUser=Save password on %S?
-saveButton=Save
-dontSaveButton=Don't save
+# String will be replaced by brandShortName.
+saveLogin=Would you like %S to remember this login?
+rememberButton=Remember
+neverButton=Never
 
 # String is the login's hostname
 updatePassword=Update saved password for %S?
 updatePasswordNoUser=Update saved password for this login?
 updateButton=Update
 dontUpdateButton=Don't update
 
 userSelectText=Please confirm which user you are changing the password for