Merge m-c to inbound, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 13 Apr 2016 14:45:41 -0700
changeset 330973 35a8ccc134cbd33f772aad47fa3c4c3f121faf68
parent 330972 6ccfb75c8926488004183b9a4bd3aa0ea66b2fba (current diff)
parent 330920 bc2373295e31d99f9b870a1253b6e01650df8f31 (diff)
child 330974 0e45dc599a04eb99f8c8e3f3156878b7e258ccee
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
    */