Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 13 Apr 2016 14:45:41 -0700
changeset 316876 35a8ccc134cbd33f772aad47fa3c4c3f121faf68
parent 316875 6ccfb75c8926488004183b9a4bd3aa0ea66b2fba (current diff)
parent 316823 bc2373295e31d99f9b870a1253b6e01650df8f31 (diff)
child 316877 0e45dc599a04eb99f8c8e3f3156878b7e258ccee
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
Merge m-c to inbound, a=merge MozReview-Commit-ID: ET0qKXDqQXD
mobile/android/base/java/org/mozilla/gecko/animation/AnimatorProxy.java
--- a/accessible/tests/mochitest/events/test_focus_autocomplete.xul
+++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul
@@ -30,24 +30,28 @@
   <script type="application/javascript"
           src="../autocomplete.js" />
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Hacky stuffs
 
-    // This is the hack needed for searchbar work outside of browser.
+    // This is the hacks needed to use a searchbar without browser.js.
     function getBrowser()
     {
       return {
         mCurrentBrowser: { engines: new Array() }
       };
     }
 
+    var BrowserSearch = {
+      updateOpenSearchBadge: function() {}
+    };
+
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
     function loadFormAutoComplete(aIFrameID)
     {
       this.iframeNode = getNode(aIFrameID);
       this.iframe = getAccessible(aIFrameID);
 
--- a/accessible/tests/mochitest/states/test_expandable.xul
+++ b/accessible/tests/mochitest/states/test_expandable.xul
@@ -57,23 +57,26 @@
       {
         // unregister 'test-a11y-search' autocomplete search
         shutdownAutoComplete();
       }
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
-    // This is the hack needed for searchbar work outside of browser.
+    // This is the hacks needed to use a searchbar without browser.js.
     function getBrowser()
     {
       return {
         mCurrentBrowser: { engines: new Array() }
       };
     }
+    var BrowserSearch = {
+      updateOpenSearchBadge: function() {}
+    };
 
     SimpleTest.waitForExplicitFinish();
 
     // Register 'test-a11y-search' autocomplete search.
     // XPFE AutoComplete needs to register early.
     initAutoComplete([ "hello", "hi" ],
                      [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]);
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3404,19 +3404,16 @@ const DOMLinkHandler = {
       return;
 
     BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL));
   },
 }
 
 const BrowserSearch = {
   addEngine: function(browser, engine, uri) {
-    if (!this.searchBar)
-      return;
-
     // Check to see whether we've already added an engine with this title
     if (browser.engines) {
       if (browser.engines.some(e => e.title == engine.title))
         return;
     }
 
     var hidden = false;
     // If this engine (identified by title) is already in the list, add it
@@ -3444,21 +3441,17 @@ const BrowserSearch = {
 
   /**
    * Update the browser UI to show whether or not additional engines are
    * available when a page is loaded or the user switches tabs to a page that
    * has search engines.
    */
   updateOpenSearchBadge: function() {
     var searchBar = this.searchBar;
-
-    // The search bar binding might not be applied even though the element is
-    // in the document (e.g. when the navigation toolbar is hidden), so check
-    // for .textbox specifically.
-    if (!searchBar || !searchBar.textbox)
+    if (!searchBar)
       return;
 
     var engines = gBrowser.selectedBrowser.engines;
     if (engines && engines.length > 0)
       searchBar.setAttribute("addengines", "true");
     else
       searchBar.removeAttribute("addengines");
   },
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -248,27 +248,27 @@ const CustomizableWidgets = [
 
       let utils = RecentlyClosedTabsAndWindowsMenuUtils;
       let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true,
                                                "menuRestoreAllTabsSubview.label");
       let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
       let elementCount = tabsFragment.childElementCount;
       separator.hidden = !elementCount;
       while (--elementCount >= 0) {
-        tabsFragment.children[elementCount].classList.add("subviewbutton");
+        tabsFragment.children[elementCount].classList.add("subviewbutton", "cui-withicon");
       }
       recentlyClosedTabs.appendChild(tabsFragment);
 
       let windowsFragment = utils.getWindowsFragment(doc.defaultView, "toolbarbutton", true,
                                                      "menuRestoreAllWindowsSubview.label");
       separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
       elementCount = windowsFragment.childElementCount;
       separator.hidden = !elementCount;
       while (--elementCount >= 0) {
-        windowsFragment.children[elementCount].classList.add("subviewbutton");
+        windowsFragment.children[elementCount].classList.add("subviewbutton", "cui-withicon");
       }
       recentlyClosedWindows.appendChild(windowsFragment);
     },
     onCreated: function(aNode) {
       // Middle clicking recently closed items won't close the panel - cope:
       let onRecentlyClosedClick = function(aEvent) {
         if (aEvent.button == 1) {
           CustomizableUI.hidePanelForNode(this);
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -76,16 +76,17 @@
         Services.search.init((function search_init_cb(aStatus) {
           // Bail out if the binding's been destroyed
           if (!this._initialized)
             return;
 
           if (Components.isSuccessCode(aStatus)) {
             // Refresh the display (updating icon, etc)
             this.updateDisplay();
+            BrowserSearch.updateOpenSearchBadge();
           } else {
             Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
           }
         }).bind(this));
       ]]></constructor>
 
       <destructor><![CDATA[
         this.destroy();
--- a/browser/themes/osx/customizableui/panelUIOverlay.css
+++ b/browser/themes/osx/customizableui/panelUIOverlay.css
@@ -1,14 +1,18 @@
 /* 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/. */
 
 %include ../../shared/customizableui/panelUIOverlay.inc.css
 
+:root {
+  --panel-separator-color: hsla(210,4%,10%,.15);
+}
+
 .panel-subviews {
   background-color: hsla(0,0%,100%,.97);
 }
 
 .panelUI-grid .toolbarbutton-1 {
   margin-right: 0;
   margin-left: 0;
   margin-bottom: 0;
@@ -68,9 +72,9 @@ menu.subviewbutton > .menu-right > image
 
 .PanelUI-subView menuseparator,
 .cui-widget-panelview menuseparator {
   padding: 0 !important;
 }
 
 toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-button {
   padding: 3px 1px;
-}
\ No newline at end of file
+}
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -16,16 +16,17 @@
 %define menuStateHover :not(:-moz-any([disabled],:active))[_moz-menuactive]
 %define buttonStateActive :not([disabled]):-moz-any([open],:hover:active)
 %define menuStateActive :not([disabled])[_moz-menuactive]:active
 %define menuStateMenuActive :not([disabled])[_moz-menuactive]
 
 %include ../browser.inc
 
 :root {
+  --panel-separator-color: ThreeDShadow;
   --panel-ui-exit-subview-gutter-width: 38px;
 }
 
 #PanelUI-popup #PanelUI-contents:empty {
   height: 128px;
 }
 
 #PanelUI-popup #PanelUI-contents:empty::before {
@@ -1148,17 +1149,18 @@ menuitem.panel-subview-footer@menuStateA
   padding-left: 6px;
 }
 
 .PanelUI-subView menuseparator,
 .PanelUI-subView toolbarseparator,
 .cui-widget-panelview menuseparator {
   -moz-appearance: none;
   min-height: 0;
-  border-top: 1px solid hsla(210,4%,10%,.15);
+  border-top: 1px solid var(--panel-separator-color);
+  border-bottom: none;
   margin: 6px 0;
   padding: 0;
 }
 
 .PanelUI-subView menuseparator,
 .PanelUI-subView toolbarseparator {
   -moz-margin-start: -5px;
   -moz-margin-end: -4px;
@@ -1341,17 +1343,17 @@ toolbarpaletteitem[haswideitem][place="p
 }
 
 .toolbaritem-combined-buttons@inAnyPanel@ > separator {
   -moz-appearance: none;
   -moz-box-align: stretch;
   margin: .5em 0;
   width: 1px;
   height: auto;
-  background: hsla(210,4%,10%,.15);
+  background: var(--panel-separator-color);
   transition-property: margin;
   transition-duration: 10ms;
   transition-timing-function: ease;
 }
 
 .toolbaritem-combined-buttons@inAnyPanel@:hover > separator {
   margin: 0;
 }
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -296,23 +296,24 @@ StyleEditorUI.prototype = {
     let editor = yield this._addStyleSheetEditor(styleSheet);
 
     if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
       return;
     }
 
     let sources = yield styleSheet.getOriginalSources();
     if (sources && sources.length) {
+      let parentEditorName = editor.friendlyName;
       this._removeStyleSheetEditor(editor);
 
       for (let source of sources) {
         // set so the first sheet will be selected, even if it's a source
         source.styleSheetIndex = styleSheet.styleSheetIndex;
         source.relatedStyleSheet = styleSheet;
-
+        source.relatedEditorName = parentEditorName;
         yield this._addStyleSheetEditor(source);
       }
     }
   }),
 
   /**
    * Add a new editor to the UI for a source.
    *
@@ -798,17 +799,17 @@ StyleEditorUI.prototype = {
    */
   _updateSummaryForEditor: function(editor, summary) {
     summary = summary || editor.summary;
     if (!summary) {
       return;
     }
 
     let ruleCount = editor.styleSheet.ruleCount;
-    if (editor.styleSheet.relatedStyleSheet && editor.linkedCSSFile) {
+    if (editor.styleSheet.relatedStyleSheet) {
       ruleCount = editor.styleSheet.relatedStyleSheet.ruleCount;
     }
     if (ruleCount === undefined) {
       ruleCount = "-";
     }
 
     let flags = [];
     if (editor.styleSheet.disabled) {
@@ -823,21 +824,23 @@ StyleEditorUI.prototype = {
     this._view.setItemClassName(summary, flags.join(" "));
 
     let label = summary.querySelector(".stylesheet-name > label");
     label.setAttribute("value", editor.friendlyName);
     if (editor.styleSheet.href) {
       label.setAttribute("tooltiptext", editor.styleSheet.href);
     }
 
-    let linkedCSSFile = "";
+    let linkedCSSSource = "";
     if (editor.linkedCSSFile) {
-      linkedCSSFile = OS.Path.basename(editor.linkedCSSFile);
+      linkedCSSSource = OS.Path.basename(editor.linkedCSSFile);
+    } else if (editor.styleSheet.relatedEditorName) {
+      linkedCSSSource = editor.styleSheet.relatedEditorName;
     }
-    text(summary, ".stylesheet-linked-file", linkedCSSFile);
+    text(summary, ".stylesheet-linked-file", linkedCSSSource);
     text(summary, ".stylesheet-title", editor.styleSheet.title || "");
     text(summary, ".stylesheet-rule-count",
       PluralForm.get(ruleCount,
                      getString("ruleCount.label")).replace("#1", ruleCount));
   },
 
   /**
    * Update the @media rules sidebar for an editor. Hide if there are no rules
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -217,16 +217,19 @@ StyleSheetEditor.prototype = {
    * If this is an original source, get the path of the CSS file it generated.
    */
   linkCSSFile: function() {
     if (!this.styleSheet.isOriginalSource) {
       return;
     }
 
     let relatedSheet = this.styleSheet.relatedStyleSheet;
+    if (!relatedSheet || !relatedSheet.href) {
+      return;
+    }
 
     let path;
     let href = removeQuery(relatedSheet.href);
     let uri = NetUtil.newURI(href);
 
     if (uri.scheme == "file") {
       let file = uri.QueryInterface(Ci.nsIFileURL).file;
       path = file.path;
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -35,16 +35,17 @@ support-files =
   sourcemap-css/media-rules.css
   sourcemap-css/media-rules.css.map
   sourcemap-css/test-bootstrap-scss.css
   sourcemap-css/test-stylus.css
   sourcemap-sass/sourcemaps.scss
   sourcemap-sass/media-rules.scss
   sourcemap-styl/test-stylus.styl
   sourcemaps.html
+  sourcemaps-inline.html
   sourcemaps-large.html
   sourcemaps-watching.html
   test_private.css
   test_private.html
   doc_long.css
   doc_uncached.css
   doc_uncached.html
   doc_xulpage.xul
@@ -79,16 +80,17 @@ skip-if = e10s && debug # Bug 1252201 - 
 [browser_styleeditor_pretty.js]
 [browser_styleeditor_private_perwindowpb.js]
 [browser_styleeditor_reload.js]
 [browser_styleeditor_scroll.js]
 [browser_styleeditor_sv_keynav.js]
 [browser_styleeditor_sv_resize.js]
 [browser_styleeditor_selectstylesheet.js]
 [browser_styleeditor_sourcemaps.js]
+[browser_styleeditor_sourcemaps_inline.js]
 [browser_styleeditor_sourcemap_large.js]
 [browser_styleeditor_sourcemap_watching.js]
 [browser_styleeditor_sync.js]
 [browser_styleeditor_syncAddRule.js]
 [browser_styleeditor_syncAlreadyOpen.js]
 [browser_styleeditor_syncEditSelector.js]
 [browser_styleeditor_syncIntoRuleView.js]
 [browser_styleeditor_transition_rule.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemaps_inline.js
@@ -0,0 +1,85 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "sourcemaps-inline.html";
+const PREF = "devtools.styleeditor.source-maps-enabled";
+
+const sassContent = `body {
+  background-color: black;
+  & > h1 {
+    color: white;
+  }
+}
+`;
+
+const cssContent = `body {
+  background-color: black;
+}
+body > h1 {
+  color: white;
+}
+` +
+"/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6IDMsCiJtY" +
+"XBwaW5ncyI6ICJBQUFBLElBQUs7RUFDSCxnQkFBZ0IsRUFBRSxLQUFLO0VBQ3ZCLFNBQU87SUFD" +
+"TCxLQUFLLEVBQUUsS0FBSyIsCiJzb3VyY2VzIjogWyJ0ZXN0LnNjc3MiXSwKInNvdXJjZXNDb25" +
+"0ZW50IjogWyJib2R5IHtcbiAgYmFja2dyb3VuZC1jb2xvcjogYmxhY2s7XG4gICYgPiBoMSB7XG" +
+"4gICAgY29sb3I6IHdoaXRlO1xuICB9XG59XG4iXSwKIm5hbWVzIjogW10sCiJmaWxlIjogInRlc" +
+"3QuY3NzIgp9Cg== */";
+
+add_task(function* () {
+  let {ui} = yield openStyleEditorForURL(TESTCASE_URI);
+
+  is(ui.editors.length, 1,
+    "correct number of editors with source maps enabled");
+
+  yield testEditor(ui.editors[0], "test.scss", sassContent);
+
+  // Test disabling original sources
+  yield togglePref(ui);
+
+  is(ui.editors.length, 1, "correct number of editors after pref toggled");
+
+  // Test CSS editors
+  yield testEditor(ui.editors[0], "<inline style sheet #1>", cssContent);
+
+  Services.prefs.clearUserPref(PREF);
+});
+
+function* testEditor(editor, expectedName, expectedText) {
+  let name = getStylesheetNameFor(editor);
+  is(expectedName, name, name + " editor name is correct");
+
+  yield openEditor(editor);
+  let text = editor.sourceEditor.getText();
+  is(text, expectedText, name + " editor contains expected text");
+}
+
+/* Helpers */
+
+function togglePref(UI) {
+  let editorsPromise = UI.once("stylesheets-reset");
+  let selectedPromise = UI.once("editor-selected");
+
+  Services.prefs.setBoolPref(PREF, false);
+
+  return promise.all([editorsPromise, selectedPromise]);
+}
+
+function openEditor(editor) {
+  getLinkFor(editor).click();
+
+  return editor.getSourceEditor();
+}
+
+function getLinkFor(editor) {
+  return editor.summary.querySelector(".stylesheet-name");
+}
+
+function getStylesheetNameFor(editor) {
+  return editor.summary.querySelector(".stylesheet-name > label")
+    .getAttribute("value");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/test/sourcemaps-inline.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>testcase for testing CSS source maps in inline style</title>
+  <style type="text/css">body {
+  background-color: black;
+}
+body > h1 {
+  color: white;
+}
+/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6IDMsCiJtYXBwaW5ncyI6ICJBQUFBLElBQUs7RUFDSCxnQkFBZ0IsRUFBRSxLQUFLO0VBQ3ZCLFNBQU87SUFDTCxLQUFLLEVBQUUsS0FBSyIsCiJzb3VyY2VzIjogWyJ0ZXN0LnNjc3MiXSwKInNvdXJjZXNDb250ZW50IjogWyJib2R5IHtcbiAgYmFja2dyb3VuZC1jb2xvcjogYmxhY2s7XG4gICYgPiBoMSB7XG4gICAgY29sb3I6IHdoaXRlO1xuICB9XG59XG4iXSwKIm5hbWVzIjogW10sCiJmaWxlIjogInRlc3QuY3NzIgp9Cg== */</style>
+</head>
+<body>
+  <h1>Source maps testcase</div>
+</body>
+</html>
--- a/devtools/server/tests/browser/browser_animation_reconstructState.js
+++ b/devtools/server/tests/browser/browser_animation_reconstructState.js
@@ -2,17 +2,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Check that, even though the AnimationPlayerActor only sends the bits of its
 // state that change, the front reconstructs the whole state everytime.
 
-add_task(function*() {
+add_task(function* () {
   let {client, walker, animations} =
     yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
 
   yield playerHasCompleteStateAtAllTimes(walker, animations);
 
   yield closeDebuggerClient(client);
   gBrowser.removeCurrentTab();
 });
@@ -24,15 +24,15 @@ function* playerHasCompleteStateAtAllTim
 
   // Get the list of state key names from the initialstate.
   let keys = Object.keys(player.initialState);
 
   // Get the state over and over again and check that the object returned
   // contains all keys.
   // Normally, only the currentTime will have changed in between 2 calls.
   for (let i = 0; i < 10; i++) {
-    let state = yield player.getCurrentState();
+    yield player.refreshState();
     keys.forEach(key => {
-      ok(typeof state[key] !== "undefined",
+      ok(typeof player.state[key] !== "undefined",
          "The state retrieved has key " + key);
     });
   }
 }
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -64,18 +64,16 @@ public class AppConstants {
          * Otherwise, we need a range check.
          */
         public static final boolean preMarshmallow = MAX_SDK_VERSION < 23 || (MIN_SDK_VERSION < 23 && Build.VERSION.SDK_INT < 23);
         public static final boolean preLollipop = MAX_SDK_VERSION < 21 || (MIN_SDK_VERSION < 21 && Build.VERSION.SDK_INT < 21);
         public static final boolean preJBMR2 = MAX_SDK_VERSION < 18 || (MIN_SDK_VERSION < 18 && Build.VERSION.SDK_INT < 18);
         public static final boolean preJBMR1 = MAX_SDK_VERSION < 17 || (MIN_SDK_VERSION < 17 && Build.VERSION.SDK_INT < 17);
         public static final boolean preJB = MAX_SDK_VERSION < 16 || (MIN_SDK_VERSION < 16 && Build.VERSION.SDK_INT < 16);
         public static final boolean preICS = MAX_SDK_VERSION < 14 || (MIN_SDK_VERSION < 14 && Build.VERSION.SDK_INT < 14);
-        public static final boolean preHCMR2 = MAX_SDK_VERSION < 13 || (MIN_SDK_VERSION < 13 && Build.VERSION.SDK_INT < 13);
-        public static final boolean preHCMR1 = MAX_SDK_VERSION < 12 || (MIN_SDK_VERSION < 12 && Build.VERSION.SDK_INT < 12);
     }
 
     /**
      * The name of the Java class that represents the android application.
      */
     public static final String MOZ_ANDROID_APPLICATION_CLASS = "@MOZ_ANDROID_APPLICATION_CLASS@";
     /**
      * The name of the Java class that launches the browser activity.
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/animation/AnimatorProxy.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/* -*- 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.animation;
-
-import java.lang.ref.WeakReference;
-import java.util.WeakHashMap;
-
-import org.mozilla.gecko.AppConstants.Versions;
-
-import android.graphics.Matrix;
-import android.graphics.RectF;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-
-class AnimatorProxy {
-    private static final WeakHashMap<View, AnimatorProxy> PROXIES =
-            new WeakHashMap<View, AnimatorProxy>();
-
-    private static interface AnimatorProxyImpl {
-        public float getAlpha();
-        public void setAlpha(float alpha);
-
-        public float getTranslationX();
-        public void setTranslationX(float translationX);
-
-        public float getTranslationY();
-        public void setTranslationY(float translationY);
-
-        public View getView();
-    }
-
-    private final AnimatorProxyImpl mImpl;
-
-    private AnimatorProxy(AnimatorProxyImpl impl) {
-        mImpl = impl;
-    }
-
-    public static AnimatorProxy create(View view) {
-        AnimatorProxy proxy = PROXIES.get(view);
-        // If the view's animation proxy has been overridden from somewhere else, we need to
-        // create a new AnimatorProxy for the view.
-        if (proxy == null) {
-            AnimatorProxyImpl impl = (new AnimatorProxyPostHC(view));
-
-            proxy = new AnimatorProxy(impl);
-            PROXIES.put(view, proxy);
-        }
-
-        return proxy;
-    }
-
-    public int getWidth() {
-        View view = mImpl.getView();
-        if (view != null)
-            return view.getWidth();
-
-        return 0;
-    }
-
-    public void setWidth(int width) {
-        View view = mImpl.getView();
-        if (view != null) {
-            ViewGroup.LayoutParams lp = view.getLayoutParams();
-            lp.width = width;
-            view.setLayoutParams(lp);
-        }
-    }
-
-    public int getHeight() {
-        View view = mImpl.getView();
-        if (view != null)
-            return view.getHeight();
-
-        return 0;
-    }
-
-    public void setHeight(int height) {
-        View view = mImpl.getView();
-        if (view != null) {
-            ViewGroup.LayoutParams lp = view.getLayoutParams();
-            lp.height = height;
-            view.setLayoutParams(lp);
-        }
-    }
-
-    public int getScrollX() {
-        View view = mImpl.getView();
-        if (view != null)
-            return view.getScrollX();
-
-        return 0;
-    }
-
-    public int getScrollY() {
-        View view = mImpl.getView();
-        if (view != null)
-            return view.getScrollY();
-
-        return 0;
-    }
-
-    public void scrollTo(int scrollX, int scrollY) {
-        View view = mImpl.getView();
-        if (view != null)
-            view.scrollTo(scrollX, scrollY);
-    }
-
-    public float getAlpha() {
-        return mImpl.getAlpha();
-    }
-
-    public void setAlpha(float alpha) {
-        mImpl.setAlpha(alpha);
-    }
-
-    public float getTranslationX() {
-        return mImpl.getTranslationX();
-    }
-
-    public void setTranslationX(float translationX) {
-        mImpl.setTranslationX(translationX);
-    }
-
-    public float getTranslationY() {
-        return mImpl.getTranslationY();
-    }
-
-    public void setTranslationY(float translationY) {
-        mImpl.setTranslationY(translationY);
-    }
-
-    private static class AnimatorProxyPostHC implements AnimatorProxyImpl {
-        private final WeakReference<View> mViewRef;
-
-        public AnimatorProxyPostHC(View view) {
-            mViewRef = new WeakReference<View>(view);
-        }
-
-        @Override
-        public float getAlpha() {
-            View view = mViewRef.get();
-            if (view != null)
-                return view.getAlpha();
-
-            return 1;
-        }
-
-        @Override
-        public void setAlpha(float alpha) {
-            View view = mViewRef.get();
-            if (view != null)
-                view.setAlpha(alpha);
-        }
-
-        @Override
-        public float getTranslationX() {
-            View view = mViewRef.get();
-            if (view != null)
-                return view.getTranslationX();
-
-            return 0;
-        }
-
-        @Override
-        public void setTranslationX(float translationX) {
-            View view = mViewRef.get();
-            if (view != null)
-                view.setTranslationX(translationX);
-        }
-
-        @Override
-        public float getTranslationY() {
-            View view = mViewRef.get();
-            if (view != null)
-                return view.getTranslationY();
-
-            return 0;
-        }
-
-        @Override
-        public void setTranslationY(float translationY) {
-            View view = mViewRef.get();
-            if (view != null)
-                view.setTranslationY(translationY);
-        }
-
-        @Override
-        public View getView() {
-            return mViewRef.get();
-        }
-    }
-}
-
--- a/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java
@@ -30,17 +30,16 @@ public class PropertyAnimator implements
         SCROLL_X,
         SCROLL_Y,
         WIDTH,
         HEIGHT
     }
 
     private class ElementHolder {
         View view;
-        AnimatorProxy proxy;
         Property property;
         float from;
         float to;
     }
 
     public static interface PropertyAnimationListener {
         public void onPropertyAnimationStart();
         public void onPropertyAnimationEnd();
@@ -71,17 +70,16 @@ public class PropertyAnimator implements
     public void setUseHardwareLayer(boolean useHardwareLayer) {
         mUseHardwareLayer = useHardwareLayer;
     }
 
     public void attach(View view, Property property, float to) {
         ElementHolder element = new ElementHolder();
 
         element.view = view;
-        element.proxy = AnimatorProxy.create(view);
         element.property = property;
         element.to = to;
 
         mElementsList.add(element);
     }
 
     public void addPropertyAnimationListener(PropertyAnimationListener listener) {
         if (mListeners == null) {
@@ -123,29 +121,29 @@ public class PropertyAnimator implements
             return;
         }
 
         mStartTime = AnimationUtils.currentAnimationTimeMillis();
 
         // Fix the from value based on current position and property
         for (ElementHolder element : mElementsList) {
             if (element.property == Property.ALPHA)
-                element.from = element.proxy.getAlpha();
+                element.from = ViewHelper.getAlpha(element.view);
             else if (element.property == Property.TRANSLATION_Y)
-                element.from = element.proxy.getTranslationY();
+                element.from = ViewHelper.getTranslationY(element.view);
             else if (element.property == Property.TRANSLATION_X)
-                element.from = element.proxy.getTranslationX();
+                element.from = ViewHelper.getTranslationX(element.view);
             else if (element.property == Property.SCROLL_Y)
-                element.from = element.proxy.getScrollY();
+                element.from = ViewHelper.getScrollY(element.view);
             else if (element.property == Property.SCROLL_X)
-                element.from = element.proxy.getScrollX();
+                element.from = ViewHelper.getScrollX(element.view);
             else if (element.property == Property.WIDTH)
-                element.from = element.proxy.getWidth();
+                element.from = ViewHelper.getWidth(element.view);
             else if (element.property == Property.HEIGHT)
-                element.from = element.proxy.getHeight();
+                element.from = ViewHelper.getHeight(element.view);
 
             ViewCompat.setHasTransientState(element.view, true);
 
             if (shouldEnableHardwareLayer(element))
                 element.view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             else
                 element.view.setDrawingCacheEnabled(true);
         }
@@ -249,29 +247,29 @@ public class PropertyAnimator implements
         final View view = element.view;
 
         // check to see if the view was detached between the check above and this code
         // getting run on the UI thread.
         if (view.getHandler() == null)
             return;
 
         if (element.property == Property.ALPHA)
-            element.proxy.setAlpha(delta);
+            ViewHelper.setAlpha(element.view, delta);
         else if (element.property == Property.TRANSLATION_Y)
-            element.proxy.setTranslationY(delta);
+            ViewHelper.setTranslationY(element.view, delta);
         else if (element.property == Property.TRANSLATION_X)
-            element.proxy.setTranslationX(delta);
+            ViewHelper.setTranslationX(element.view, delta);
         else if (element.property == Property.SCROLL_Y)
-            element.proxy.scrollTo(element.proxy.getScrollX(), (int) delta);
+            ViewHelper.scrollTo(element.view, ViewHelper.getScrollX(element.view), (int) delta);
         else if (element.property == Property.SCROLL_X)
-            element.proxy.scrollTo((int) delta, element.proxy.getScrollY());
+            ViewHelper.scrollTo(element.view, (int) delta, ViewHelper.getScrollY(element.view));
         else if (element.property == Property.WIDTH)
-            element.proxy.setWidth((int) delta);
+            ViewHelper.setWidth(element.view, (int) delta);
         else if (element.property == Property.HEIGHT)
-            element.proxy.setHeight((int) delta);
+            ViewHelper.setHeight(element.view, (int) delta);
     }
 
     private static abstract class FramePoster {
         public static FramePoster create(Runnable r) {
             if (Versions.feature16Plus) {
                 return new FramePosterPostJB(r);
             }
 
--- a/mobile/android/base/java/org/mozilla/gecko/animation/ViewHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/animation/ViewHelper.java
@@ -1,62 +1,109 @@
 /* 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.animation;
 
 import android.view.View;
+import android.view.ViewGroup;
 
 public final class ViewHelper {
     private ViewHelper() {
     }
 
     public static float getTranslationX(View view) {
-        AnimatorProxy proxy = AnimatorProxy.create(view);
-        return proxy.getTranslationX();
+        if (view != null) {
+            return view.getTranslationX();
+        }
+
+        return 0;
     }
 
     public static void setTranslationX(View view, float translationX) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        proxy.setTranslationX(translationX);
+        if (view != null) {
+            view.setTranslationX(translationX);
+        }
     }
 
     public static float getTranslationY(View view) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        return proxy.getTranslationY();
+        if (view != null) {
+            return view.getTranslationY();
+        }
+
+        return 0;
     }
 
     public static void setTranslationY(View view, float translationY) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        proxy.setTranslationY(translationY);
+        if (view != null) {
+            view.setTranslationY(translationY);
+        }
     }
 
     public static float getAlpha(View view) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        return proxy.getAlpha();
+        if (view != null) {
+            return view.getAlpha();
+        }
+
+        return 1;
     }
 
     public static void setAlpha(View view, float alpha) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        proxy.setAlpha(alpha);
+        if (view != null) {
+            view.setAlpha(alpha);
+        }
     }
 
     public static int getWidth(View view) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        return proxy.getWidth();
+        if (view != null) {
+            return view.getWidth();
+        }
+
+        return 0;
     }
 
     public static void setWidth(View view, int width) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        proxy.setWidth(width);
+        if (view != null) {
+            ViewGroup.LayoutParams lp = view.getLayoutParams();
+            lp.width = width;
+            view.setLayoutParams(lp);
+        }
     }
 
     public static int getHeight(View view) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        return proxy.getHeight();
+        if (view != null) {
+            return view.getHeight();
+        }
+
+        return 0;
     }
 
     public static void setHeight(View view, int height) {
-        final AnimatorProxy proxy = AnimatorProxy.create(view);
-        proxy.setHeight(height);
+        if (view != null) {
+            ViewGroup.LayoutParams lp = view.getLayoutParams();
+            lp.height = height;
+            view.setLayoutParams(lp);
+        }
+    }
+
+    public static int getScrollX(View view) {
+        if (view != null) {
+            return view.getScrollX();
+        }
+
+        return 0;
+    }
+
+    public static int getScrollY(View view) {
+        if (view != null) {
+            return view.getScrollY();
+        }
+
+        return 0;
+    }
+
+    public static void scrollTo(View view, int scrollX, int scrollY) {
+        if (view != null) {
+            view.scrollTo(scrollX, scrollY);
+        }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
+++ b/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
@@ -892,17 +892,20 @@ public class Distribution {
                         baseDirectory + "/" + mcc + "/" + mnc,
                         baseDirectory + "/" + mcc,
                         baseDirectory + "/default",
                         baseDirectory
                 };
             }
         }
 
-        return new String[] { baseDirectory };
+        return new String[] {
+                baseDirectory + "/default",
+                baseDirectory
+        };
     }
 
     /**
      * The provided <code>ReadyCallback</code> will be queued for execution after
      * the distribution is ready, or queued for immediate execution if the
      * distribution has already been processed.
      *
      * Each <code>ReadyCallback</code> will be executed on the background thread.
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/JavaPanZoomController.java
@@ -1,17 +1,16 @@
 /* -*- 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.gfx;
 
 import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.util.FloatUtils;
@@ -300,20 +299,16 @@ class JavaPanZoomController
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     /** This function MUST be called on the UI thread */
     @Override
     public boolean onKeyEvent(KeyEvent event) {
-        if (Versions.preHCMR1) {
-            return false;
-        }
-
         if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
             && event.getAction() == KeyEvent.ACTION_DOWN) {
 
             switch (event.getKeyCode()) {
             case KeyEvent.KEYCODE_ZOOM_IN:
                 return animatedScale(0.2f);
             case KeyEvent.KEYCODE_ZOOM_OUT:
                 return animatedScale(-0.2f);
@@ -323,20 +318,16 @@ class JavaPanZoomController
     }
 
     // Ignore MontionEvent velocity. Needed for C++APZ
     public void onMotionEventVelocity(final long aEventTime, final float aSpeedY) {}
 
     /** This function MUST be called on the UI thread */
     @Override
     public boolean onMotionEvent(MotionEvent event) {
-        if (Versions.preHCMR1) {
-            return false;
-        }
-
         switch (event.getSource() & InputDevice.SOURCE_CLASS_MASK) {
         case InputDevice.SOURCE_CLASS_POINTER:
             switch (event.getAction() & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_SCROLL: return handlePointerScroll(event);
             }
             break;
         case InputDevice.SOURCE_CLASS_JOYSTICK:
             switch (event.getAction() & MotionEvent.ACTION_MASK) {
--- a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenu.java
@@ -457,16 +457,27 @@ public class GeckoMenu extends ListView
                 !mQuickShareActionItems.containsKey(menuItem))
                 return true;
         }
 
         return false;
     }
 
     @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Close the menu if it is open and the hardware menu key is pressed.
+        if (keyCode == KeyEvent.KEYCODE_MENU && isShown()) {
+            close();
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
     public boolean isShortcutKey(int keyCode, KeyEvent event) {
         return true;
     }
 
     @Override
     public boolean performIdentifierAction(int id, int flags) {
         return false;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/util/HardwareUtils.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/HardwareUtils.java
@@ -13,24 +13,16 @@ import android.content.pm.PackageManager
 import android.content.res.Configuration;
 import android.os.Build;
 import android.util.Log;
 import android.view.ViewConfiguration;
 
 public final class HardwareUtils {
     private static final String LOGTAG = "GeckoHardwareUtils";
 
-    // Minimum memory threshold for a device to be considered
-    // a low memory platform (see isLowMemoryPlatform). This value
-    // has be in sync with Gecko's equivalent threshold (defined in
-    // xpcom/base/nsMemoryImpl.cpp) and should only be used in cases
-    // where we can't depend on Gecko to be up and running e.g. show/hide
-    // reading list capabilities in HomePager.
-    private static final int LOW_MEMORY_THRESHOLD_MB = 384;
-
     private static final boolean IS_AMAZON_DEVICE = Build.MANUFACTURER.equalsIgnoreCase("Amazon");
     public static final boolean IS_KINDLE_DEVICE = IS_AMAZON_DEVICE &&
                                                    (Build.MODEL.equals("Kindle Fire") ||
                                                     Build.MODEL.startsWith("KF"));
 
     private static volatile boolean sInited;
 
     // These are all set once, during init.
@@ -81,29 +73,16 @@ public final class HardwareUtils {
     public static boolean isTelevision() {
         return sIsTelevision;
     }
 
     public static int getMemSize() {
         return SysInfo.getMemSize();
     }
 
-    public static boolean isLowMemoryPlatform() {
-        final int memSize = getMemSize();
-
-        // Fallback to false if we fail to read meminfo
-        // for some reason.
-        if (memSize == 0) {
-            Log.w(LOGTAG, "Could not compute system memory. Falling back to isLowMemoryPlatform = false.");
-            return false;
-        }
-
-        return memSize < LOW_MEMORY_THRESHOLD_MB;
-    }
-
     /**
      * @return false if the current system is not supported (e.g. APK/system ABI mismatch).
      */
     public static boolean isSupportedSystem() {
         if (Build.VERSION.SDK_INT < AppConstants.Versions.MIN_SDK_VERSION ||
             Build.VERSION.SDK_INT > AppConstants.Versions.MAX_SDK_VERSION) {
             return false;
         }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -214,23 +214,27 @@
 <!ENTITY pref_header_help "Help">
 <!ENTITY pref_header_vendor "&vendorShortName;">
 
 <!ENTITY pref_cookies_menu "Cookies">
 <!ENTITY pref_cookies_accept_all "Enabled">
 <!ENTITY pref_cookies_not_accept_foreign "Enabled, excluding 3rd party">
 <!ENTITY pref_cookies_disabled "Disabled">
 
+<!ENTITY pref_category_data_saver "Data saver">
+<!ENTITY pref_category_media "Media">
+<!ENTITY pref_category_developer_tools "Developer tools">
+
 <!ENTITY pref_tap_to_load_images_title2 "Show images">
 <!ENTITY pref_tap_to_load_images_enabled "Always">
 <!ENTITY pref_tap_to_load_images_data "Only over Wi-Fi">
 <!ENTITY pref_tap_to_load_images_disabled2 "Blocked">
 
-<!ENTITY pref_show_web_fonts "Show Web fonts">
-<!ENTITY pref_show_web_fonts_summary "Hide to use default fonts and reduce website load times">
+<!ENTITY pref_show_web_fonts "Show web fonts">
+<!ENTITY pref_show_web_fonts_summary2 "Download remote fonts when loading a page">
 
 <!ENTITY pref_tracking_protection_title2 "Tracking Protection">
 <!ENTITY pref_tracking_protection_summary3 "Enabled in Private Browsing">
 <!ENTITY pref_donottrack_title "Do not track">
 <!ENTITY pref_donottrack_summary "&brandShortName; will tell sites that you do not want to be tracked">
 
 <!ENTITY pref_tracking_protection_enabled "Enabled">
 <!ENTITY pref_tracking_protection_enabled_pb "Enabled in Private Browsing">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -191,17 +191,16 @@ gbjar.sources += ['java/org/mozilla/geck
     'AccountsHelper.java',
     'ActionBarTextSelection.java',
     'ActionModeCompat.java',
     'ActionModeCompatView.java',
     'ActivityHandlerHelper.java',
     'AlarmReceiver.java',
     'AndroidGamepadManager.java',
     'animation/AnimationUtils.java',
-    'animation/AnimatorProxy.java',
     'animation/HeightChangeAnimation.java',
     'animation/PropertyAnimator.java',
     'animation/Rotate3DAnimation.java',
     'animation/ViewHelper.java',
     'ANRReporter.java',
     'AppNotificationClient.java',
     'BaseGeckoInterface.java',
     'BootReceiver.java',
--- a/mobile/android/base/resources/xml/preferences_advanced.xml
+++ b/mobile/android/base/resources/xml/preferences_advanced.xml
@@ -27,47 +27,57 @@
 
     <ListPreference android:key="android.not_a_preference.restoreSession3"
                     android:title="@string/pref_restore"
                     android:defaultValue="always"
                     android:entries="@array/pref_restore_entries"
                     android:entryValues="@array/pref_restore_values"
                     android:persistent="true" />
 
-    <ListPreference android:key="browser.image_blocking"
-                    android:title="@string/pref_tap_to_load_images_title2"
-                    android:entries="@array/pref_browser_image_blocking_entries"
-                    android:entryValues="@array/pref_browser_image_blocking_values"
-                    android:persistent="false" />
-
-    <CheckBoxPreference android:key="browser.display.use_document_fonts"
-                        android:title="@string/pref_show_web_fonts"
-                        android:summary="@string/pref_show_web_fonts_summary"/>
-
-    <ListPreference android:key="plugin.enable"
-                    android:title="@string/pref_plugins"
-                    android:entries="@array/pref_plugins_entries"
-                    android:entryValues="@array/pref_plugins_values"
-                    android:persistent="false" />
-
-    <SwitchPreference android:key="media.autoplay.enabled"
-                      android:title="@string/pref_media_autoplay_enabled"
-                      android:summary="@string/pref_media_autoplay_enabled_summary" />
-
     <ListPreference android:key="browser.menu.showCharacterEncoding"
                     android:title="@string/pref_char_encoding"
                     android:entries="@array/pref_char_encoding_entries"
                     android:entryValues="@array/pref_char_encoding_values"
                     android:persistent="false" />
 
-    <SwitchPreference android:key="devtools.remote.usb.enabled"
-                      android:title="@string/pref_developer_remotedebugging_usb" />
+    <PreferenceCategory android:title="@string/pref_category_data_saver">
+
+        <ListPreference android:key="browser.image_blocking"
+                        android:title="@string/pref_tap_to_load_images_title2"
+                        android:entries="@array/pref_browser_image_blocking_entries"
+                        android:entryValues="@array/pref_browser_image_blocking_values"
+                        android:persistent="false" />
 
+        <SwitchPreference android:key="browser.display.use_document_fonts"
+                          android:title="@string/pref_show_web_fonts"
+                          android:summary="@string/pref_show_web_fonts_summary"/>
+
+    </PreferenceCategory>
+
+    <PreferenceCategory android:title="@string/pref_category_media">
 
-    <SwitchPreference android:key="devtools.remote.wifi.enabled"
-                      android:title="@string/pref_developer_remotedebugging_wifi" />
+        <ListPreference android:key="plugin.enable"
+                        android:title="@string/pref_plugins"
+                        android:entries="@array/pref_plugins_entries"
+                        android:entryValues="@array/pref_plugins_values"
+                        android:persistent="false" />
+
+        <SwitchPreference android:key="media.autoplay.enabled"
+                          android:title="@string/pref_media_autoplay_enabled"
+                          android:summary="@string/pref_media_autoplay_enabled_summary" />
+
+    </PreferenceCategory>
 
-    <org.mozilla.gecko.preferences.AlignRightLinkPreference android:key="android.not_a_preference.remote_debugging.link"
-                                                            android:title="@string/pref_learn_more"
-                                                            android:persistent="false"
-                                                            url="https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_Firefox_for_Android_with_WebIDE" />
+    <PreferenceCategory android:title="@string/pref_category_developer_tools">
+
+        <SwitchPreference android:key="devtools.remote.usb.enabled"
+                          android:title="@string/pref_developer_remotedebugging_usb" />
+
+        <SwitchPreference android:key="devtools.remote.wifi.enabled"
+                          android:title="@string/pref_developer_remotedebugging_wifi" />
+
+        <org.mozilla.gecko.preferences.AlignRightLinkPreference android:key="android.not_a_preference.remote_debugging.link"
+                                                                android:title="@string/pref_learn_more"
+                                                                android:persistent="false"
+                                                                url="https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_Firefox_for_Android_with_WebIDE" />
+    </PreferenceCategory>
 
 </PreferenceScreen>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -198,23 +198,27 @@
 
   <string name="pref_manage_logins">&pref_manage_logins;</string>
 
   <string name="pref_cookies_menu">&pref_cookies_menu;</string>
   <string name="pref_cookies_accept_all">&pref_cookies_accept_all;</string>
   <string name="pref_cookies_not_accept_foreign">&pref_cookies_not_accept_foreign;</string>
   <string name="pref_cookies_disabled">&pref_cookies_disabled;</string>
 
+  <string name="pref_category_data_saver">&pref_category_data_saver;</string>
+  <string name="pref_category_media">&pref_category_media;</string>
+  <string name="pref_category_developer_tools">&pref_category_developer_tools;</string>
+
   <string name="pref_tap_to_load_images_title2">&pref_tap_to_load_images_title2;</string>
   <string name="pref_tap_to_load_images_enabled">&pref_tap_to_load_images_enabled;</string>
   <string name="pref_tap_to_load_images_data">&pref_tap_to_load_images_data;</string>
   <string name="pref_tap_to_load_images_disabled2">&pref_tap_to_load_images_disabled2;</string>
 
   <string name="pref_show_web_fonts">&pref_show_web_fonts;</string>
-  <string name="pref_show_web_fonts_summary">&pref_show_web_fonts_summary;</string>
+  <string name="pref_show_web_fonts_summary">&pref_show_web_fonts_summary2;</string>
 
   <string name="pref_tracking_protection_title">&pref_tracking_protection_title2;</string>
   <string name="pref_tracking_protection_summary">&pref_tracking_protection_summary3;</string>
   <string name="pref_donottrack_title">&pref_donottrack_title;</string>
   <string name="pref_donottrack_summary">&pref_donottrack_summary;</string>
 
   <string name="pref_tracking_protection_enabled">&pref_tracking_protection_enabled;</string>
   <string name="pref_tracking_protection_enabled_pb">&pref_tracking_protection_enabled_pb;</string>
--- a/mobile/android/chrome/content/ActionBarHandler.js
+++ b/mobile/android/chrome/content/ActionBarHandler.js
@@ -617,18 +617,18 @@ var ActionBarHandler = {
     // Textarea can contain LF, etc.
     if (this._targetElement instanceof Ci.nsIDOMHTMLTextAreaElement) {
       let flags = Ci.nsIDocumentEncoder.OutputPreformatted |
         Ci.nsIDocumentEncoder.OutputRaw;
       return selection.QueryInterface(Ci.nsISelectionPrivate).
         toStringWithFormat("text/plain", flags, 0);
     }
 
-    // Selection text gets trimmed up.
-    return selection.toString().trim();
+    // Return explicitly selected text.
+    return selection.toString();
   },
 
   /**
    * Provides the nsISelection for either an editor, or from the
    * default window.
    */
   _getSelection: function(element = this._targetElement, win = this._contentWindow) {
     return (element instanceof Ci.nsIDOMNSEditableElement) ?
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -1346,17 +1346,23 @@ LoginManagerPrompter.prototype = {
     var ok = this._promptService.select(this._window,
                             dialogTitle, dialogText,
                             usernames.length, usernames,
                             selectedIndex);
     if (ok) {
       // Now that we know which login to use, modify its password.
       var selectedLogin = logins[selectedIndex.value];
       this.log("Updating password for user " + selectedLogin.username);
-      this._updateLogin(selectedLogin, aNewLogin);
+      var newLoginWithUsername = Cc["@mozilla.org/login-manager/loginInfo;1"].
+                     createInstance(Ci.nsILoginInfo);
+      newLoginWithUsername.init(aNewLogin.hostname,
+                                aNewLogin.formSubmitURL, aNewLogin.httpRealm,
+                                selectedLogin.username, aNewLogin.password,
+                                selectedLogin.userNameField, aNewLogin.passwordField);
+      this._updateLogin(selectedLogin, newLoginWithUsername);
     }
   },
 
 
 
 
   /* ---------- Internal Methods ---------- */
 
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -7,25 +7,28 @@ support-files =
   ../subtst_notifications_2pw_1un_1text.html
   ../subtst_notifications_3.html
   ../subtst_notifications_4.html
   ../subtst_notifications_5.html
   ../subtst_notifications_6.html
   ../subtst_notifications_8.html
   ../subtst_notifications_9.html
   ../subtst_notifications_10.html
+  ../subtst_notifications_change_p.html
   authenticate.sjs
   form_basic.html
   head.js
   insecure_test.html
   insecure_test_subframe.html
   multiple_forms.html
   streamConverter_content.sjs
 
 [browser_capture_doorhanger.js]
+[browser_username_select_dialog.js]
+skip-if = e10s # bug 1263760
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 [browser_filldoorhanger.js]
 [browser_hasInsecureLoginForms.js]
 [browser_hasInsecureLoginForms_streamConverter.js]
 [browser_notifications.js]
 skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
 [browser_passwordmgr_editing.js]
--- a/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_capture_doorhanger.js
@@ -46,47 +46,65 @@ add_task(function* test_clickNever() {
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-save");
     ok(notif, "got notification popup");
     is(true, Services.logins.getLoginSavingEnabled("http://mochi.test:8888"),
        "Checking for login saving enabled");
     clickDoorhangerButton(notif, NEVER_BUTTON);
   });
 
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
+
   info("Make sure Never took effect");
   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, "checking for no notification popup");
     is(false, Services.logins.getLoginSavingEnabled("http://mochi.test:8888"),
        "Checking for login saving disabled");
     Services.logins.setLoginSavingEnabled("http://mochi.test:8888", true);
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 add_task(function* test_clickRemember() {
   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");
 
     is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
     clickDoorhangerButton(notif, REMEMBER_BUTTON);
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username used on the new entry");
+  is(login.password, "notifyp1", "Check the password used on the new entry");
+  is(login.timesUsed, 1, "Check times used on new entry");
+
   info("Make sure Remember took effect and we don't prompt for an existing login");
   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, "checking for no notification popup");
   });
 
+  logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username used");
+  is(login.password, "notifyp1", "Check the password used");
+  is(login.timesUsed, 2, "Check times used incremented");
+
   checkOnlyLoginWasUsedTwice({ justChanged: false });
 
   // remove that login
   Services.logins.removeLogin(login1);
 });
 
 /* signons.rememberSignons pref tests... */
 
@@ -95,153 +113,200 @@ add_task(function* test_rememberSignonsF
   Services.prefs.setBoolPref("signon.rememberSignons", false);
 
   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, "checking for no notification popup");
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 add_task(function* test_rememberSignonsTrue() {
   info("Make sure we prompt with rememberSignons=true");
   Services.prefs.setBoolPref("signon.rememberSignons", true);
 
   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");
     notif.remove();
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 /* autocomplete=off tests... */
 
 add_task(function* test_autocompleteOffUsername() {
   info("Check for notification popup when autocomplete=off present on username");
 
   yield testSubmittingLoginForm("subtst_notifications_2.html", function*(fieldValues) {
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-save");
     ok(notif, "checking for notification popup");
     notif.remove();
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 add_task(function* test_autocompleteOffPassword() {
   info("Check for notification popup when autocomplete=off present on password");
 
   yield testSubmittingLoginForm("subtst_notifications_3.html", function*(fieldValues) {
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-save");
     ok(notif, "checking for notification popup");
     notif.remove();
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 add_task(function* test_autocompleteOffForm() {
   info("Check for notification popup when autocomplete=off present on form");
 
   yield testSubmittingLoginForm("subtst_notifications_4.html", function*(fieldValues) {
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-save");
     ok(notif, "checking for notification popup");
     notif.remove();
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 
 add_task(function* test_noPasswordField() {
   info("Check for no notification popup when no password field present");
 
   yield testSubmittingLoginForm("subtst_notifications_5.html", function*(fieldValues) {
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "null", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-save");
     ok(!notif, "checking for no notification popup");
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 add_task(function* test_pwOnlyLoginMatchesForm() {
   info("Check for update popup when existing pw-only login matches form.");
   Services.logins.addLogin(login2);
 
   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-change");
     ok(notif, "checking for notification popup");
     notif.remove();
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "", "Check the username");
+  is(login.password, "notifyp1", "Check the password");
+  is(login.timesUsed, 1, "Check times used");
+
   Services.logins.removeLogin(login2);
 });
 
 add_task(function* test_pwOnlyFormMatchesLogin() {
   info("Check for no notification popup when pw-only form matches existing login.");
   Services.logins.addLogin(login1);
 
   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, "checking for no notification popup");
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username");
+  is(login.password, "notifyp1", "Check the password");
+  is(login.timesUsed, 2, "Check times used");
+
   Services.logins.removeLogin(login1);
 });
 
 add_task(function* test_pwOnlyFormDoesntMatchExisting() {
   info("Check for notification popup when pw-only form doesn't match existing login.");
   Services.logins.addLogin(login1B);
 
   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");
     notif.remove();
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1B", "Check the username unchanged");
+  is(login.password, "notifyp1B", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
   Services.logins.removeLogin(login1B);
 });
 
 add_task(function* test_changeUPLoginOnUPForm_dont() {
   info("Check for change-password popup, u+p login on u+p form. (not changed)");
   Services.logins.addLogin(login1);
 
   yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "pass2", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change");
     ok(notif, "got notification popup");
     clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "notifyp1", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
   Services.logins.removeLogin(login1);
 });
 
 add_task(function* test_changeUPLoginOnUPForm_change() {
   info("Check for change-password popup, u+p login on u+p form.");
   Services.logins.addLogin(login1);
 
   yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "pass2", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change");
     ok(notif, "got notification popup");
     clickDoorhangerButton(notif, CHANGE_BUTTON);
     ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "pass2", "Check the password changed");
+  is(login.timesUsed, 2, "Check times used");
+
   checkOnlyLoginWasUsedTwice({ justChanged: true });
 
   // cleanup
   login1.password = "pass2";
   Services.logins.removeLogin(login1);
   login1.password = "notifyp1";
 });
 
@@ -252,29 +317,46 @@ add_task(function* test_changePLoginOnUP
   yield testSubmittingLoginForm("subtst_notifications_9.html", function*(fieldValues) {
     is(fieldValues.username, "", "Checking submitted username");
     is(fieldValues.password, "pass2", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change");
     ok(notif, "got notification popup");
     clickDoorhangerButton(notif, CHANGE_BUTTON);
     ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
   });
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "", "Check the username unchanged");
+  is(login.password, "pass2", "Check the password changed");
+  is(login.timesUsed, 2, "Check times used");
+
+  // no cleanup -- saved password to be used in the next test.
 });
 
 add_task(function* test_changePLoginOnPForm() {
   info("Check for change-password popup, p-only login on p-only form.");
 
   yield testSubmittingLoginForm("subtst_notifications_10.html", function*(fieldValues) {
     is(fieldValues.username, "null", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change");
     ok(notif, "got notification popup");
     clickDoorhangerButton(notif, CHANGE_BUTTON);
     ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
   });
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "", "Check the username unchanged");
+  is(login.password, "notifyp1", "Check the password changed");
+  is(login.timesUsed, 3, "Check times used");
+
   Services.logins.removeLogin(login2);
 });
 
 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");
@@ -282,61 +364,74 @@ add_task(function* test_checkUPSaveText(
     let notif = getCaptureDoorhanger("password-save");
     ok(notif, "got notification popup");
     // Check the text, which comes from the localized saveLoginText string.
     let notificationText = notif.message;
     let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this login?";
     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.
     let notificationText = notif.message;
     let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this password?";
     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() {
   info("Check for notification popup when a form with 2 password fields (no username) " +
        "is submitted and there are no saved logins.");
 
   yield testSubmittingLoginForm("subtst_notifications_2pw_0un.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");
     notif.remove();
   });
+
+  is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
 });
 
 add_task(function* test_change2pw0unExistingDifferentUP() {
   info("Check for notification popup when a form with 2 password fields (no username) " +
        "is submitted and there is a saved login with a username and different password.");
 
   Services.logins.addLogin(login1B);
 
   yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
     is(fieldValues.username, "null", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change");
     ok(notif, "got notification popup");
     notif.remove();
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1B", "Check the username unchanged");
+  is(login.password, "notifyp1B", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
   Services.logins.removeLogin(login1B);
 });
 
 add_task(function* test_change2pw0unExistingDifferentP() {
   info("Check for notification popup when a form with 2 password fields (no username) " +
        "is submitted and there is a saved login with no username and different password.");
 
   Services.logins.addLogin(login2B);
@@ -344,67 +439,119 @@ add_task(function* test_change2pw0unExis
   yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
     is(fieldValues.username, "null", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change");
     ok(notif, "got notification popup");
     notif.remove();
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "", "Check the username unchanged");
+  is(login.password, "notifyp1B", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
   Services.logins.removeLogin(login2B);
 });
 
 add_task(function* test_change2pw0unExistingWithSameP() {
   info("Check for no notification popup when a form with 2 password fields (no username) " +
        "is submitted and there is a saved login with a username and the same password.");
 
   Services.logins.addLogin(login2);
 
   yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
     is(fieldValues.username, "null", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change");
     ok(!notif, "checking for no notification popup");
   });
 
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "", "Check the username unchanged");
+  is(login.password, "notifyp1", "Check the password unchanged");
+  is(login.timesUsed, 2, "Check times used incremented");
+
   checkOnlyLoginWasUsedTwice({ justChanged: false });
 
   Services.logins.removeLogin(login2);
 });
 
+add_task(function* test_changeUPLoginOnPUpdateForm() {
+  info("Check for change-password popup, u+p login on password update form.");
+  Services.logins.addLogin(login1);
+
+  yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+    let notif = getCaptureDoorhanger("password-change");
+    ok(notif, "got notification popup");
+    clickDoorhangerButton(notif, CHANGE_BUTTON);
+    ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
+  });
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "pass2", "Check the password changed");
+  is(login.timesUsed, 2, "Check times used");
+
+  checkOnlyLoginWasUsedTwice({ justChanged: true });
+
+  // cleanup
+  login1.password = "pass2";
+  Services.logins.removeLogin(login1);
+  login1.password = "notifyp1";
+});
+
 add_task(function* test_recipeCaptureFields_NewLogin() {
   info("Check that we capture the proper fields when a field recipe is in use.");
 
   yield testSubmittingLoginForm("subtst_notifications_2pw_1un_1text.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");
 
     // Sanity check, no logins should exist yet.
     let logins = Services.logins.getAllLogins();
     is(logins.length, 0, "Should not have any logins yet");
 
     clickDoorhangerButton(notif, REMEMBER_BUTTON);
   }, "http://example.org"); // The recipe is for example.org
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "notifyp1", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
 });
 
 add_task(function* test_recipeCaptureFields_ExistingLogin() {
   info("Check that we capture the proper fields when a field recipe is in use " +
        "and there is a matching login");
 
   yield testSubmittingLoginForm("subtst_notifications_2pw_1un_1text.html", function*(fieldValues) {
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-save");
     ok(!notif, "checking for no notification popup");
   }, "http://example.org");
 
   checkOnlyLoginWasUsedTwice({ justChanged: false });
   let logins = Services.logins.getAllLogins();
-  is(logins[0].username, "notifyu1", "check .username for existing login submission");
-  is(logins[0].password, "notifyp1", "check .password for existing login submission");
+  is(logins.length, 1, "Should only have 1 login");
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "notifyp1", "Check the password unchanged");
+  is(login.timesUsed, 2, "Check times used incremented");
 
   Services.logins.removeAllLogins();
 });
 
 // TODO:
 // * existing login test, form has different password --> change password, no save prompt
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_username_select_dialog.js
@@ -0,0 +1,144 @@
+/*
+ * Test username selection dialog, on password update from a p-only form,
+ * when there are multiple saved logins on the domain.
+ */
+
+// Copied from prompt_common.js. TODO: share the code.
+function getSelectDialogDoc() {
+  // Trudge through all the open windows, until we find the one
+  // that has selectDialog.xul loaded.
+  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+           getService(Ci.nsIWindowMediator);
+  //var enumerator = wm.getEnumerator("navigator:browser");
+  var enumerator = wm.getXULWindowEnumerator(null);
+
+  while (enumerator.hasMoreElements()) {
+    var win = enumerator.getNext();
+    var windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+
+    var containedDocShells = windowDocShell.getDocShellEnumerator(
+                                      Ci.nsIDocShellTreeItem.typeChrome,
+                                      Ci.nsIDocShell.ENUMERATE_FORWARDS);
+    while (containedDocShells.hasMoreElements()) {
+        // Get the corresponding document for this docshell
+        var childDocShell = containedDocShells.getNext();
+        // We don't want it if it's not done loading.
+        if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+          continue;
+        var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
+                                    .contentViewer
+                                    .DOMDocument;
+
+        if (childDoc.location.href == "chrome://global/content/selectDialog.xul")
+          return childDoc;
+    }
+  }
+
+  return null;
+}
+
+let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+                                             Ci.nsILoginInfo, "init");
+let login1 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                             "notifyu1", "notifyp1", "user", "pass");
+let login1B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                              "notifyu1B", "notifyp1B", "user", "pass");
+
+add_task(function* test_changeUPLoginOnPUpdateForm_accept() {
+  info("Select an u+p login from multiple logins, on password update form, and accept.");
+  Services.logins.addLogin(login1);
+  Services.logins.addLogin(login1B);
+
+  yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return getSelectDialogDoc();
+    }, "Wait for selection dialog to be accessible.");
+
+    let doc = getSelectDialogDoc();
+    let dialog = doc.getElementsByTagName("dialog")[0];
+    let listbox = doc.getElementById("list");
+
+    is(listbox.selectedIndex, 0, "Checking selected index");
+    is(listbox.itemCount, 2, "Checking selected length");
+    ['notifyu1', 'notifyu1B'].forEach((username, i) => {
+      is(listbox.getItemAtIndex(i).label, username, "Check username selection on dialog");
+    });
+
+    dialog.acceptDialog();
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return !getSelectDialogDoc();
+    }, "Wait for selection dialog to disappear.");
+  });
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 2, "Should have 2 logins");
+
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "pass2", "Check the password changed");
+  is(login.timesUsed, 2, "Check times used");
+
+  login = SpecialPowers.wrap(logins[1]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1B", "Check the username unchanged");
+  is(login.password, "notifyp1B", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
+  // cleanup
+  login1.password = "pass2";
+  Services.logins.removeLogin(login1);
+  login1.password = "notifyp1";
+
+  Services.logins.removeLogin(login1B);
+});
+
+add_task(function* test_changeUPLoginOnPUpdateForm_cancel() {
+  info("Select an u+p login from multiple logins, on password update form, and cancel.");
+  Services.logins.addLogin(login1);
+  Services.logins.addLogin(login1B);
+
+  yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return getSelectDialogDoc();
+    }, "Wait for selection dialog to be accessible.");
+
+    let doc = getSelectDialogDoc();
+    let dialog = doc.getElementsByTagName("dialog")[0];
+    let listbox = doc.getElementById("list");
+
+    is(listbox.selectedIndex, 0, "Checking selected index");
+    is(listbox.itemCount, 2, "Checking selected length");
+    ['notifyu1', 'notifyu1B'].forEach((username, i) => {
+      is(listbox.getItemAtIndex(i).label, username, "Check username selection on dialog");
+    });
+
+    dialog.cancelDialog();
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return !getSelectDialogDoc();
+    }, "Wait for selection dialog to disappear.");
+  });
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 2, "Should have 2 logins");
+
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "notifyp1", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
+  login = SpecialPowers.wrap(logins[1]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1B", "Check the username unchanged");
+  is(login.password, "notifyp1B", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
+  // cleanup
+  Services.logins.removeLogin(login1);
+  Services.logins.removeLogin(login1B);
+});
--- a/toolkit/components/passwordmgr/test/browser/head.js
+++ b/toolkit/components/passwordmgr/test/browser/head.js
@@ -1,11 +1,12 @@
 const DIRECTORY_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
 
 Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
+Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
 
 registerCleanupFunction(function* cleanup_removeAllLoginsAndResetRecipes() {
   Services.logins.removeAllLogins();
   let recipeParent = LoginTestUtils.recipes.getRecipeParent();
   if (!recipeParent) {
     // No need to reset the recipes if the recipe module wasn't even loaded.
     return;
   }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/subtst_notifications_change_p.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Subtest for Login Manager notifications</title>
+</head>
+<body>
+<h2>Change password</h2>
+<form id="form" action="formsubmit.sjs">
+  <input id="pass_current" name="pass_current" type="password" value="notifyp1">
+  <input id="pass" name="pass" type="password">
+  <input id="pass_confirm" name="pass_confirm" type="password">
+  <button type='submit'>Submit</button>
+</form>
+
+<script>
+function submitForm() {
+  passField.value = "pass2";
+  passConfirmField.value = "pass2";
+
+  form.submit();
+}
+
+window.onload = submitForm;
+var form      = document.getElementById("form");
+var userField = document.getElementById("user");
+var passField = document.getElementById("pass");
+var passConfirmField = document.getElementById("pass_confirm");
+
+</script>
+</body>
+</html>
--- a/toolkit/components/passwordmgr/test/test_notifications_popup.html
+++ b/toolkit/components/passwordmgr/test/test_notifications_popup.html
@@ -76,30 +76,33 @@ function checkTest() {
         clickPopupButton(popup, kRememberButton);
         break;
 
       case 2:
         // Check result of clicking kRememberButton
         logins = pwmgr.getAllLogins();
         is(logins.length, 1, "Should only have 1 login");
         login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
-        ok(login.timesUsed, 1, "Check times used on new entry");
+        is(login.username, "notifyu1", "Check the username used on the new entry");
+        is(login.password, "notifyp1", "Check the password used on the new entry");
+        is(login.timesUsed, 1, "Check times used on new entry");
 
         // password-change with chrome hidden
         popup = getPopup(popupNotifications, "password-change");
         ok(popup, "got notification popup");
         clickPopupButton(popup, kChangeButton);
         break;
 
       case 3:
         // Check to make sure we updated the password, timestamps and use count for
         // the login being changed with this form.
         logins = pwmgr.getAllLogins();
         is(logins.length, 1, "Should only have 1 login");
         login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+        is(login.username, "notifyu1", "Check the username");
         is(login.password, "pass2", "Check password changed");
         is(login.timesUsed, 2, "check .timesUsed incremented on change");
         ok(login.timeCreated < login.timeLastUsed, "timeLastUsed bumped");
         ok(login.timeLastUsed == login.timePasswordChanged, "timeUsed == timeChanged");
 
         login1.password = "pass2";
         pwmgr.removeLogin(login1);
         login1.password = "notifyp1";
@@ -115,17 +118,19 @@ function checkTest() {
         popupWin.close();
         break;
 
       case 4:
         // Check result of clicking kRememberButton
         logins = pwmgr.getAllLogins();
         is(logins.length, 1, "Should only have 1 login now");
         login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
-        ok(login.timesUsed, 1, "Check times used on new entry");
+        is(login.username, "notifyu2", "Check the username used on the new entry");
+        is(login.password, "notifyp2", "Check the password used on the new entry");
+        is(login.timesUsed, 1, "Check times used on new entry");
 
         // password-change with chrome visible
         popupWin = iframe.contentWindow.popupWin;
         ok(popupWin, "Check popupWin is accessible");
         popupNotificationsInPopup = getPopupNotifications(popupWin);
         ok(popupNotificationsInPopup, "Got popupNotificationsInPopup");
         popup = getPopup(popupNotificationsInPopup, "password-change");
         ok(popup, "got notification popup");
@@ -134,16 +139,17 @@ function checkTest() {
         break;
 
       case 5:
         // Check to make sure we updated the password, timestamps and use count for
         // the login being changed with this form.
         logins = pwmgr.getAllLogins();
         is(logins.length, 1, "Should have 1 login");
         login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+        is(login.username, "notifyu2", "Check the username");
         is(login.password, "pass2", "Check password changed");
         is(login.timesUsed, 2, "check .timesUsed incremented on change");
         ok(login.timeCreated < login.timeLastUsed, "timeLastUsed bumped");
         ok(login.timeLastUsed == login.timePasswordChanged, "timeUsed == timeChanged");
 
         // cleanup
         SpecialPowers.removeObserver(PasswordMgrObserver, "passwordmgr-storage-changed");
         popupNotifications.panel.removeEventListener("popupshown", checkTest, false);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5098,16 +5098,44 @@
     "description": "Set if Telemetry failed to validate the session data loaded from disk."
   },
   "TELEMETRY_SESSIONDATA_FAILED_SAVE": {
     "alert_emails": ["telemetry-client-dev@mozilla.com"],
     "expires_in_version": "never",
     "kind": "flag",
     "description": "Set if Telemetry failed to save the session data to disk."
   },
+  "TELEMETRY_ASSEMBLE_PAYLOAD_EXCEPTION": {
+    "alert_emails": ["telemetry-client-dev@mozilla.com"],
+    "bug_numbers": [1250640],
+    "expires_in_version": "53",
+    "kind": "count",
+    "description": "Count of exceptions in TelemetrySession.getSessionPayload()."
+  },
+  "TELEMETRY_SCHEDULER_TICK_EXCEPTION": {
+    "alert_emails": ["telemetry-client-dev@mozilla.com"],
+    "bug_numbers": [1250640],
+    "expires_in_version": "53",
+    "kind": "count",
+    "description": "Count of exceptions during executing the TelemetrySession scheduler tick logic."
+  },
+  "TELEMETRY_SCHEDULER_WAKEUP": {
+    "alert_emails": ["telemetry-client-dev@mozilla.com"],
+    "bug_numbers": [1250640],
+    "expires_in_version": "53",
+    "kind": "count",
+    "description": "Count of TelemetrySession scheduler ticks that were delayed long enough to suspect sleep."
+  },
+  "TELEMETRY_SCHEDULER_SEND_DAILY": {
+    "alert_emails": ["telemetry-client-dev@mozilla.com"],
+    "bug_numbers": [1250640],
+    "expires_in_version": "53",
+    "kind": "count",
+    "description": "Count of TelemetrySession triggering a daily ping."
+  },
   "TELEMETRY_TEST_FLAG": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "a testing histogram; not meant to be touched"
   },
   "TELEMETRY_TEST_COUNT": {
     "expires_in_version": "never",
     "kind": "count",
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -315,29 +315,33 @@ var TelemetryScheduler = {
     // Otherwise, we might end up sending daily pings even if the subsession is not long enough.
     let now = Policy.now();
     this._lastDailyPingTime = now.getTime();
     this._lastSessionCheckpointTime = now.getTime();
     this._rescheduleTimeout();
     idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
   },
 
+  _clearTimeout: function() {
+    if (this._schedulerTimer) {
+      Policy.clearSchedulerTickTimeout(this._schedulerTimer);
+    }
+  },
+
   /**
    * Reschedules the tick timer.
    */
   _rescheduleTimeout: function() {
     this._log.trace("_rescheduleTimeout - isUserIdle: " + this._isUserIdle);
     if (this._shuttingDown) {
       this._log.warn("_rescheduleTimeout - already shutdown");
       return;
     }
 
-    if (this._schedulerTimer) {
-      Policy.clearSchedulerTickTimeout(this._schedulerTimer);
-    }
+    this._clearTimeout();
 
     const now = Policy.now();
     let timeout = SCHEDULER_TICK_INTERVAL_MS;
 
     // When the user is idle we want to fire the timer less often.
     if (this._isUserIdle) {
       timeout = SCHEDULER_TICK_IDLE_INTERVAL_MS;
       // We need to make sure though that we don't miss sending pings around
@@ -416,25 +420,30 @@ var TelemetryScheduler = {
   },
 
   /**
    * Performs a scheduler tick. This function manages Telemetry recurring operations.
    * @return {Promise} A promise, only used when testing, resolved when the scheduled
    *                   operation completes.
    */
   _onSchedulerTick: function() {
+    // This call might not be triggered from a timeout. In that case we don't want to
+    // leave any previously scheduled timeouts pending.
+    this._clearTimeout();
+
     if (this._shuttingDown) {
       this._log.warn("_onSchedulerTick - already shutdown.");
       return Promise.reject(new Error("Already shutdown."));
     }
 
     let promise = Promise.resolve();
     try {
       promise = this._schedulerTickLogic();
     } catch (e) {
+      Telemetry.getHistogramById("TELEMETRY_SCHEDULER_TICK_EXCEPTION").add(1);
       this._log.error("_onSchedulerTick - There was an exception", e);
     } finally {
       this._rescheduleTimeout();
     }
 
     // This promise is returned to make testing easier.
     return promise;
   },
@@ -444,25 +453,28 @@ var TelemetryScheduler = {
    * @return {Promise} Resolved when the scheduled task completes. Only used in tests.
    */
   _schedulerTickLogic: function() {
     this._log.trace("_schedulerTickLogic");
 
     let nowDate = Policy.now();
     let now = nowDate.getTime();
 
-    if (now - this._lastTickTime > 1.1 * SCHEDULER_TICK_INTERVAL_MS) {
-      this._log.trace("_schedulerTickLogic - First scheduler tick after sleep or startup.");
+    if ((now - this._lastTickTime) > (1.1 * SCHEDULER_TICK_INTERVAL_MS) &&
+        (this._lastTickTime != 0)) {
+      Telemetry.getHistogramById("TELEMETRY_SCHEDULER_WAKEUP").add(1);
+      this._log.trace("_schedulerTickLogic - First scheduler tick after sleep.");
     }
     this._lastTickTime = now;
 
     // Check if the daily ping is due.
     const shouldSendDaily = this._isDailyPingDue(nowDate);
 
     if (shouldSendDaily) {
+      Telemetry.getHistogramById("TELEMETRY_SCHEDULER_SEND_DAILY").add(1);
       this._log.trace("_schedulerTickLogic - Daily ping due.");
       this._lastDailyPingTime = now;
       return Impl._sendDailyPing();
     }
 
     // Check if the aborted-session ping is due. If a daily ping was saved above, it was
     // already duplicated as an aborted-session ping.
     const isAbortedPingDue =
@@ -1294,33 +1306,39 @@ var Impl = {
     this._subsessionId = Policy.generateSubsessionUUID();
     this._subsessionCounter++;
     this._profileSubsessionCounter++;
   },
 
   getSessionPayload: function getSessionPayload(reason, clearSubsession) {
     this._log.trace("getSessionPayload - reason: " + reason + ", clearSubsession: " + clearSubsession);
 
-    const isMobile = ["gonk", "android"].includes(AppConstants.platform);
-    const isSubsession = isMobile ? false : !this._isClassicReason(reason);
+    let payload;
+    try {
+      const isMobile = ["gonk", "android"].includes(AppConstants.platform);
+      const isSubsession = isMobile ? false : !this._isClassicReason(reason);
 
-    if (isMobile) {
-      clearSubsession = false;
-    }
+      if (isMobile) {
+        clearSubsession = false;
+      }
 
-    let measurements =
-      this.getSimpleMeasurements(reason == REASON_SAVED_SESSION, isSubsession, clearSubsession);
-    let info = !Utils.isContentProcess ? this.getMetadata(reason) : null;
-    let payload = this.assemblePayloadWithMeasurements(measurements, info, reason, clearSubsession);
-
-    if (!Utils.isContentProcess && clearSubsession) {
-      this.startNewSubsession();
-      // Persist session data to disk (don't wait until it completes).
-      let sessionData = this._getSessionDataObject();
-      TelemetryStorage.saveSessionData(sessionData);
+      let measurements =
+        this.getSimpleMeasurements(reason == REASON_SAVED_SESSION, isSubsession, clearSubsession);
+      let info = !Utils.isContentProcess ? this.getMetadata(reason) : null;
+      payload = this.assemblePayloadWithMeasurements(measurements, info, reason, clearSubsession);
+    } catch (ex) {
+      Telemetry.getHistogramById("TELEMETRY_ASSEMBLE_PAYLOAD_EXCEPTION").add(1);
+      throw ex;
+    } finally {
+      if (!Utils.isContentProcess && clearSubsession) {
+        this.startNewSubsession();
+        // Persist session data to disk (don't wait until it completes).
+        let sessionData = this._getSessionDataObject();
+        TelemetryStorage.saveSessionData(sessionData);
+      }
     }
 
     return payload;
   },
 
   /**
    * Send data to the server. Record success/send-time in histograms
    */