Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 28 Nov 2014 14:29:23 +0100
changeset 243888 c77bd6051d3b64f2677e507b05475f4a62ed3c2b
parent 243887 dec2bf69c4988c6b6289e34605bf694085ed5339 (current diff)
parent 242283 ca89fe55717059e4e43040d16d260765ffa9dca7 (diff)
child 243889 5d5569a7fb8cefe2d82b7afedc1777345975fcb8
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone36.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 mozilla-central to b2g-inbound
image/src/DiscardTracker.cpp
image/src/DiscardTracker.h
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -324,25 +324,16 @@ pref("media.fragmented-mp4.gonk.enabled"
 #endif
 // The default number of decoded video frames that are enqueued in
 // MediaDecoderReader's mVideoQueue.
 pref("media.video-queue.default-size", 3);
 
 // optimize images' memory usage
 pref("image.mem.decodeondraw", true);
 pref("image.mem.allow_locking_in_content_processes", false); /* don't allow image locking */
-pref("image.mem.min_discard_timeout_ms", 86400000); /* 24h, we rely on the out of memory hook */
-// At this point 'max_decoded_image_kb' only applies to animated images. They're
-// unfortunately fairly large, so this pref still needs to be somewhat generous,
-// but it makes sense to reduce it since most types of images are now in the
-// surface cache. Once animated images are stored in the surface cache too, this
-// pref will go away; see bug 977459. The same goes for
-// 'hard_limit_decoded_image_kb'; the surface cache limits are all hard.
-pref("image.mem.max_decoded_image_kb", 30000);
-pref("image.mem.hard_limit_decoded_image_kb", 66560);
 // Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
 // Almost everything that was factored into 'max_decoded_image_kb' is now stored
 // in the surface cache.  1/8 of main memory is 32MB on a 256MB device, which is
 // about the same as the old 'max_decoded_image_kb'.
 pref("image.mem.surfacecache.max_size_kb", 131072);  // 128MB
 pref("image.mem.surfacecache.size_factor", 8);  // 1/8 of main memory
 pref("image.mem.surfacecache.discard_factor", 2);  // Discard 1/2 of the surface cache at a time.
 pref("image.mem.surfacecache.min_expiration_ms", 86400000); // 24h, we rely on the out of memory hook
--- a/b2g/simulator/lib/main.js
+++ b/b2g/simulator/lib/main.js
@@ -29,25 +29,27 @@ function close() {
   if (!process) {
     return promise.resolve();
   }
   let p = process;
   process = null;
   return p.kill();
 }
 
-let appinfo = {};
+let name;
 
 AddonManager.getAddonByID(require("addon").id, function (addon) {
-  appinfo.label = addon.name.replace(" Simulator", "");
+  name = addon.name.replace(" Simulator", "");
 
-  Simulator.register(appinfo.label, {
-    appinfo: appinfo,
+  Simulator.register(name, {
+    // We keep the deprecated `appinfo` object so that recent simulator addons
+    // remain forward-compatible with older Firefox.
+    appinfo: { label: name },
     launch: launch,
     close: close
   });
 });
 
 exports.shutdown = function () {
-  Simulator.unregister(appinfo.label);
+  Simulator.unregister(name);
   close();
 }
 
--- a/browser/base/content/abouthome/aboutHome.css
+++ b/browser/base/content/abouthome/aboutHome.css
@@ -71,16 +71,46 @@ a {
 
 #searchEngineLogo {
   display: inline-block;
   height: 28px;
   width: 70px;
   min-width: 70px;
 }
 
+#searchIcon {
+  border: 1px solid transparent;
+  -moz-margin-end: 5px;
+  height: 38px;
+  width: 38px;
+  background-image: url("chrome://browser/skin/magnifier.png");
+  background-size: 26px;
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+
+#searchIcon[active],
+#searchIcon:hover {
+  background-color: #e9e9e9;
+  border: 1px solid rgb(226, 227, 229);
+  border-radius: 2.5px;
+}
+
+html[searchUIConfiguration="oldsearchui"] #searchIcon {
+  display: none;
+}
+
+html:not([searchUIConfiguration="oldsearchui"]) #searchText::-moz-placeholder {
+  color: transparent;
+}
+
+html:not([searchUIConfiguration="oldsearchui"]) #searchLogoContainer {
+  display: none;
+}
+
 #searchText {
   -moz-box-flex: 1;
   padding: 6px 8px;
   background: hsla(0,0%,100%,.9) padding-box;
   border: 1px solid;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
               0 0 2px hsla(210,65%,9%,.1) inset,
@@ -363,16 +393,20 @@ body[narrow] #restorePreviousSession::be
  * At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
  * rather than upscaling the original-size ones (bug 818940).
  */
 @media not all and (max-resolution: 1dppx) {
   #brandLogo {
     background-image: url("chrome://branding/content/about-logo@2x.png");
   }
 
+  #searchIcon {
+    background-image: url("chrome://browser/skin/magnifier@2x.png");
+  }
+
   #defaultSnippet1,
   #defaultSnippet2,
   #rightsSnippet {
     background-size: 40px;
   }
 
   #defaultSnippet1 {
     background-image: url("chrome://browser/content/abouthome/snippet1@2x.png");
--- a/browser/base/content/abouthome/aboutHome.xhtml
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -36,17 +36,18 @@
 
   <body dir="&locale.dir;">
     <div class="spacer"/>
     <div id="topSection">
       <div id="brandLogo"></div>
 
       <div id="searchContainer">
         <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)">
-          <div id="searchLogoContainer"><img id="searchEngineLogo"/></div>
+          <div id="searchLogoContainer" hidden="true"><img id="searchEngineLogo"/></div>
+          <button id="searchIcon" type="button" />
           <input type="text" name="q" value="" id="searchText" maxlength="256"
                  autofocus="autofocus" dir="auto"/>
           <input id="searchSubmit" type="submit" value="&abouthome.searchEngineButton.label;"/>
         </form>
       </div>
 
       <div id="snippetContainer">
         <div id="defaultSnippets" hidden="true">
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1216,16 +1216,39 @@ toolbarpaletteitem[place="palette"][hidd
   animation-name: uitour-zoom;
   animation-duration: 1s;
 }
 #UITourHighlight[active="color"] {
   animation-name: uitour-color;
   animation-duration: 2s;
 }
 
+#abouthome-search-panel .panel-arrowcontent {
+  -moz-padding-start: 0;
+  -moz-padding-end: 0;
+  padding-top: 0;
+  padding-bottom: 0;
+  background: rgb(248, 250, 251);
+  font-size: 110%;
+}
+
+.abouthome-search-panel-item {
+  -moz-box-align: center;
+  padding-top: 4px;
+  padding-bottom: 4px;
+  -moz-padding-start: 24px;
+  -moz-padding-end: 24px;
+}
+
+.abouthome-search-panel-item > label {
+  -moz-padding-start: 0;
+  -moz-margin-start: 0;
+  color: rgb(130, 132, 133);
+}
+
 /* Combined context-menu items */
 #context-navigation > .menuitem-iconic > .menu-iconic-text,
 #context-navigation > .menuitem-iconic > .menu-accel-container {
   display: none;
 }
 
 /* Tracking protection doorhanger */
 .popup-notification-footer[popupid="bad-content"] {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -271,16 +271,24 @@
       <hbox class="SearchHighlightFooter" align="center">
         <spacer class="dot"/>
         <spacer class="dot filled"/>
         <spacer flex="1"/>
         <button label="&SearchHighlightClose;"/>
       </hbox>
     </panel>
 
+    <panel id="abouthome-search-panel" orient="vertical" type="arrow" hidden="true"
+           onclick="this.hidePopup()">
+      <hbox id="abouthome-search-panel-manage" class="abouthome-search-panel-item"
+            onclick="openPreferences('paneSearch')">
+        <label>&changeSearchSettings.button;</label>
+      </hbox>
+    </panel>
+
     <panel id="social-share-panel"
            class="social-panel"
            type="arrow"
            orient="horizontal"
            onpopupshowing="SocialShare.onShowing()"
            onpopuphidden="SocialShare.onHidden()"
            hidden="true">
       <vbox class="social-share-toolbar">
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -264,16 +264,19 @@ let AboutHomeListener = {
     }
     switch (aEvent.type) {
       case "AboutHomeLoad":
         this.onPageLoad();
         break;
       case "AboutHomeSearchEvent":
         this.onSearch(aEvent);
         break;
+      case "AboutHomeSearchPanel":
+        this.onOpenSearchPanel(aEvent);
+        break;
       case "click":
         this.onClick(aEvent);
         break;
       case "pagehide":
         this.onPageHide(aEvent);
         break;
     }
   },
@@ -315,24 +318,29 @@ let AboutHomeListener = {
     }
 
     doc.documentElement.setAttribute("hasBrowserHandlers", "true");
     addMessageListener("AboutHome:Update", this);
     addMessageListener("AboutHome:FocusInput", this);
     addEventListener("click", this, true);
     addEventListener("pagehide", this, true);
 
+    if (!Services.prefs.getBoolPref("browser.search.showOneOffButtons")) {
+      doc.documentElement.setAttribute("searchUIConfiguration", "oldsearchui");
+    }
+
     // XXX bug 738646 - when Marketplace is launched, remove this statement and
     // the hidden attribute set on the apps button in aboutHome.xhtml
     if (Services.prefs.getPrefType("browser.aboutHome.apps") == Services.prefs.PREF_BOOL &&
         Services.prefs.getBoolPref("browser.aboutHome.apps"))
       doc.getElementById("apps").removeAttribute("hidden");
 
     sendAsyncMessage("AboutHome:RequestUpdate");
     doc.addEventListener("AboutHomeSearchEvent", this, true, true);
+    doc.addEventListener("AboutHomeSearchPanel", this, true, true);
   },
 
   onClick: function(aEvent) {
     if (!aEvent.isTrusted || // Don't trust synthetic events
         aEvent.button == 2 || aEvent.target.localName != "button") {
       return;
     }
 
@@ -373,16 +381,20 @@ let AboutHomeListener = {
 
       case "sync":
         sendAsyncMessage("AboutHome:Sync");
         break;
 
       case "settings":
         sendAsyncMessage("AboutHome:Settings");
         break;
+
+      case "searchIcon":
+        sendAsyncMessage("AboutHome:OpenSearchPanel", null, { anchor: originalTarget });
+        break;
     }
   },
 
   onPageHide: function(aEvent) {
     if (aEvent.target.defaultView.frameElement) {
       return;
     }
     removeMessageListener("AboutHome:Update", this);
@@ -392,16 +404,20 @@ let AboutHomeListener = {
       aEvent.target.documentElement.removeAttribute("hasBrowserHandlers");
     }
   },
 
   onSearch: function(aEvent) {
     sendAsyncMessage("AboutHome:Search", { searchData: aEvent.detail });
   },
 
+  onOpenSearchPanel: function(aEvent) {
+    sendAsyncMessage("AboutHome:OpenSearchPanel");
+  },
+
   onFocusInput: function () {
     let searchInput = content.document.getElementById("searchText");
     if (searchInput) {
       searchInput.focus();
     }
   },
 };
 AboutHomeListener.init(this);
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -340,16 +340,29 @@ input[type=button] {
   border: 1px solid transparent;
   -moz-margin-end: 8px;
   background-repeat: no-repeat;
   background-position: center;
   background-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
   background-size: 26px 26px;
 }
 
+#newtab-search-logo.magnifier {
+  width: 38px; /* 26 image width + 6 left "padding" + 6 right "padding" */
+  -moz-margin-end: 5px;
+  background-size: 26px;
+  background-image: url("chrome://browser/skin/magnifier.png");
+}
+
+@media not all and (max-resolution: 1dppx) {
+  #newtab-search-icon.magnifier {
+    background-image: url("chrome://browser/skin/magnifier@2x.png");
+  }
+}
+
 #newtab-search-logo[type="logo"] {
   background-size: 65px 26px;
   width: 77px; /* 65 image width + 6 left "padding" + 6 right "padding" */
 }
 
 #newtab-search-logo[type="favicon"] {
   background-size: 16px 16px;
 }
@@ -361,17 +374,17 @@ input[type=button] {
 #newtab-search-logo[active],
 #newtab-search-logo:hover {
   background-color: #e9e9e9;
   border: 1px solid rgb(226, 227, 229);
   border-radius: 2.5px;
 }
 
 #newtab-search-text {
-  height: 38px;
+  height: 38px; /* same height as #newtab-search-logo */
   -moz-box-flex: 1;
 
   padding: 0 8px;
   background: hsla(0,0%,100%,.9) padding-box;
   border: 1px solid;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
               0 0 2px hsla(210,65%,9%,.1) inset,
@@ -385,17 +398,17 @@ input[type=button] {
 }
 
 #newtab-search-text:focus,
 #newtab-search-text[autofocus] {
   border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
 }
 
 #newtab-search-submit {
-  height: 38px;
+  height: 38px; /* same height as #newtab-search-logo */
   font-size: 13px !important;
 
   -moz-margin-start: -1px;
   background: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
   padding: 0 9px;
   border: 1px solid;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   -moz-border-start: 1px solid transparent;
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -9,31 +9,33 @@
 <?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
   %newTabDTD;
   <!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd">
   %searchBarDTD;
+  <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+  %browserDTD;
 ]>
 
 <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             title="&newtab.pageTitle;">
 
   <xul:panel id="newtab-intro-panel" orient="vertical" type="arrow"
              noautohide="true" hidden="true">
     <h1>&newtab.intro.header;</h1>
   </xul:panel>
 
   <xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
              noautohide="true" hidden="true">
     <xul:hbox id="newtab-search-manage" class="newtab-search-panel-engine">
-      <xul:label>&cmd_engineManager.label;</xul:label>
+      <xul:label>&changeSearchSettings.button;</xul:label>
     </xul:hbox>
   </xul:panel>
 
   <xul:panel id="newtab-customize-panel" orient="vertical" type="arrow"
              noautohide="true" hidden="true">
     <xul:hbox id="newtab-customize-enhanced" class="newtab-customize-panel-item">
       <xul:label>&newtab.customize.enhanced;</xul:label>
     </xul:hbox>
--- a/browser/base/content/newtab/search.js
+++ b/browser/base/content/newtab/search.js
@@ -3,22 +3,33 @@
  * 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/. */
 #endif
 
 let gSearch = {
 
   currentEngineName: null,
 
+  get useNewUI() {
+    let newUI = Services.prefs.getBoolPref("browser.search.showOneOffButtons");
+    delete this.useNewUI;
+    this.useNewUI = newUI;
+    return newUI;
+  },
+
   init: function () {
     for (let idSuffix of this._nodeIDSuffixes) {
       this._nodes[idSuffix] =
         document.getElementById("newtab-search-" + idSuffix);
     }
 
+    if (this.useNewUI) {
+      this._nodes.logo.classList.add("magnifier");
+    }
+
     window.addEventListener("ContentSearchService", this);
     this._send("GetState");
   },
 
   showPanel: function () {
     let panel = this._nodes.panel;
     let logo = this._nodes.logo;
     panel.hidden = false;
@@ -117,16 +128,21 @@ let gSearch = {
       detail: {
         type: type,
         data: data,
       },
     }));
   },
 
   _setUpPanel: function () {
+    // The new search UI only contains the "manage" engine entry in the panel
+    if (this.useNewUI) {
+      return;
+    }
+
     // Build the panel if necessary.
     if (this._newEngines) {
       this._buildPanel(this._newEngines);
       delete this._newEngines;
     }
 
     // Set the selected states of the engines.
     let panel = this._nodes.panel;
@@ -193,38 +209,40 @@ let gSearch = {
     box.appendChild(label);
 
     return box;
   },
 
   _setCurrentEngine: function (engine) {
     this.currentEngineName = engine.name;
 
-    let type = "";
-    let uri;
-    let logoBuf = window.devicePixelRatio >= 2 ?
-                  engine.logo2xBuffer || engine.logoBuffer :
-                  engine.logoBuffer || engine.logo2xBuffer;
-    if (logoBuf) {
-      uri = URL.createObjectURL(new Blob([logoBuf]));
-      type = "logo";
+    if (!this.useNewUI) {
+      let type = "";
+      let uri;
+      let logoBuf = window.devicePixelRatio >= 2 ?
+                    engine.logo2xBuffer || engine.logoBuffer :
+                    engine.logoBuffer || engine.logo2xBuffer;
+      if (logoBuf) {
+        uri = URL.createObjectURL(new Blob([logoBuf]));
+        type = "logo";
+      }
+      else if (engine.iconBuffer) {
+        uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
+        type = "favicon";
+      }
+      this._nodes.logo.setAttribute("type", type);
+
+      if (uri) {
+        this._nodes.logo.style.backgroundImage = "url(" + uri + ")";
+      }
+      else {
+        this._nodes.logo.style.backgroundImage = "";
+      }
+      this._nodes.text.placeholder = engine.placeholder;
     }
-    else if (engine.iconBuffer) {
-      uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
-      type = "favicon";
-    }
-    this._nodes.logo.setAttribute("type", type);
-
-    if (uri) {
-      this._nodes.logo.style.backgroundImage = "url(" + uri + ")";
-    }
-    else {
-      this._nodes.logo.style.backgroundImage = "";
-    }
-    this._nodes.text.placeholder = engine.placeholder;
 
     // Set up the suggestion controller.
     if (!this._suggestionController) {
       let parent = document.getElementById("newtab-scrollbox");
       this._suggestionController =
         new SearchSuggestionUIController(this._nodes.text, parent,
                                          () => this.search());
     }
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -73,35 +73,16 @@ let gTests = [
     ok(snippetsElt, "Found snippets element");
     is(snippetsElt.getElementsByTagName("span").length, 1,
        "A default snippet is present.");
 
     aSnippetsMap.delete("snippets");
   }
 },
 
-{
-  desc: "Check that search engine logo has alt text",
-  setup: function () { },
-  run: function ()
-  {
-    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
-
-    let searchEngineLogoElt = doc.getElementById("searchEngineLogo");
-    ok(searchEngineLogoElt, "Found search engine logo");
-
-    let altText = searchEngineLogoElt.alt;
-    ok(typeof altText == "string" && altText.length > 0,
-       "Search engine logo's alt text is a nonempty string");
-
-    isnot(altText, "undefined",
-          "Search engine logo's alt text shouldn't be the string 'undefined'");
-  }
-},
-
 // Disabled on Linux for intermittent issues with FHR, see Bug 945667.
 {
   desc: "Check that performing a search fires a search event and records to " +
         "Firefox Health Report.",
   setup: function () { },
   run: function* () {
     // Skip this test on Linux.
     if (navigator.platform.indexOf("Linux") == 0) {
@@ -248,62 +229,16 @@ let gTests = [
     ok(snippetsElt, "Found snippets element");
     ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", "Snippet link should not point to about:rights.");
 
     Services.prefs.clearUserPref("browser.rights.override");
   }
 },
 
 {
-  desc: "Check that the search UI/ action is updated when the search engine is changed",
-  setup: function() {},
-  run: function()
-  {
-    let currEngine = Services.search.currentEngine;
-    let unusedEngines = [].concat(Services.search.getVisibleEngines()).filter(x => x != currEngine);
-    let searchbar = document.getElementById("searchbar");
-
-    function checkSearchUI(engine) {
-      let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
-      let searchText = doc.getElementById("searchText");
-      let logoElt = doc.getElementById("searchEngineLogo");
-      let engineName = doc.documentElement.getAttribute("searchEngineName");
-
-      is(engineName, engine.name, "Engine name should've been updated");
-
-      if (!logoElt.parentNode.hidden) {
-        is(logoElt.alt, engineName, "Alt text of logo image should match search engine name")
-      } else {
-        is(searchText.placeholder, engineName, "Placeholder text should match search engine name");
-      }
-    }
-    // Do a sanity check that all attributes are correctly set to begin with
-    checkSearchUI(currEngine);
-
-    let deferred = Promise.defer();
-    promiseBrowserAttributes(gBrowser.selectedTab).then(function() {
-      // Test if the update propagated
-      checkSearchUI(unusedEngines[0]);
-      searchbar.currentEngine = currEngine;
-      deferred.resolve();
-    });
-
-    // The following cleanup function will set currentEngine back to the previous
-    // engine if we fail to do so above.
-    registerCleanupFunction(function() {
-      searchbar.currentEngine = currEngine;
-    });
-    // Set the current search engine to an unused one
-    searchbar.currentEngine = unusedEngines[0];
-    searchbar.select();
-    return deferred.promise;
-  }
-},
-
-{
   desc: "Check POST search engine support",
   setup: function() {},
   run: function()
   {
     let deferred = Promise.defer();
     let currEngine = Services.search.defaultEngine;
     let searchObserver = function search_observer(aSubject, aTopic, aData) {
       let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
@@ -487,16 +422,36 @@ let gTests = [
   run: Task.async(function* () {
     let syncButton = gBrowser.selectedTab.linkedBrowser.contentDocument.getElementById("sync");
     yield EventUtils.synthesizeMouseAtCenter(syncButton, {}, gBrowser.contentWindow);
 
     yield promiseTabLoadEvent(gBrowser.selectedTab, null, "load");
     is(gBrowser.currentURI.spec, "about:accounts?entrypoint=abouthome",
       "Entry point should be `abouthome`.");
   })
+},
+{
+  desc: "Clicking the icon should open the popup",
+  setup: function () {},
+  run: Task.async(function* () {
+    let doc = gBrowser.selectedBrowser.contentDocument;
+    let searchIcon = doc.getElementById("searchIcon");
+    let panel = window.document.getElementById("abouthome-search-panel");
+
+    info("Waiting for popup to open");
+    EventUtils.synthesizeMouseAtCenter(searchIcon, {}, gBrowser.selectedBrowser.contentWindow);
+    yield promiseWaitForEvent(panel, "popupshown");
+    ok("Saw popup open");
+
+    let promise = promisePrefsOpen();
+    let item = window.document.getElementById("abouthome-search-panel-manage");
+    EventUtils.synthesizeMouseAtCenter(item, {});
+
+    yield promise;
+  })
 }
 
 ];
 
 function test()
 {
   waitForExplicitFinish();
   requestLongerTimeout(2);
@@ -662,16 +617,34 @@ function waitForLoad(cb) {
       return;
     info("Page loaded: " + browser.currentURI.spec);
     browser.removeEventListener("load", listener, true);
 
     cb();
   }, true);
 }
 
+function promiseWaitForEvent(node, type, capturing) {
+  return new Promise((resolve) => {
+    node.addEventListener(type, function listener(event) {
+      node.removeEventListener(type, listener, capturing);
+      resolve(event);
+    }, capturing);
+  });
+}
+
+let promisePrefsOpen = Task.async(function*() {
+  info("Waiting for the preferences tab to open...");
+  let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
+  let tab = event.target;
+  yield promiseTabLoadEvent(tab);
+  is(tab.linkedBrowser.currentURI.spec, "about:preferences#search", "Should have seen the prefs tab");
+  gBrowser.removeTab(tab);
+});
+
 function promiseNewEngine(basename) {
   info("Waiting for engine to be added: " + basename);
   let addDeferred = Promise.defer();
   let url = getRootDirectory(gTestPath) + basename;
   Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
     onSuccess: function (engine) {
       info("Search engine added: " + basename);
       registerCleanupFunction(() => {
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -114,51 +114,22 @@ let runTaskifiedTests = Task.async(funct
   yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
 
   // Click the logo to open the search panel.
   yield Promise.all([
     promisePanelShown(panel),
     promiseClick(logoImg()),
   ]);
 
-  // In the search panel, click the no-logo engine.  It should become the
-  // current engine.
-  let noLogoBox = null;
-  for (let box of panel.childNodes) {
-    if (box.getAttribute("engine") == noLogoEngine.name) {
-      noLogoBox = box;
-      break;
-    }
-  }
-  ok(noLogoBox, "Search panel should contain the no-logo engine");
-  yield Promise.all([
-    promiseSearchEvents(["CurrentEngine"]),
-    promiseClick(noLogoBox),
-  ]);
-
-  yield checkCurrentEngine(ENGINE_NO_LOGO);
-
-  // Switch back to the 1x-and-2x logo engine.
-  Services.search.currentEngine = logo1x2xEngine;
-  yield promiseSearchEvents(["CurrentEngine"]);
-  yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
-
-  // Open the panel again.
-  yield Promise.all([
-    promisePanelShown(panel),
-    promiseClick(logoImg()),
-  ]);
-
-  // In the search panel, click the Manage Engines box.
   let manageBox = $("manage");
   ok(!!manageBox, "The Manage Engines box should be present in the document");
-  yield Promise.all([
-    promiseManagerOpen(),
-    promiseClick(manageBox),
-  ]);
+  is(panel.childNodes.length, 1, "Search panel should only contain the Manage Engines entry");
+  is(panel.childNodes[0], manageBox, "Search panel should contain the Manage Engines entry");
+
+  panel.hidePopup();
 
   // Add the engine that provides search suggestions and switch to it.
   let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
   Services.search.currentEngine = suggestionEngine;
   yield promiseSearchEvents(["CurrentEngine"]);
   yield checkCurrentEngine(ENGINE_SUGGESTIONS);
 
   // Avoid intermittent failures.
@@ -335,52 +306,16 @@ let checkCurrentEngine = Task.async(func
   let engine = Services.search.currentEngine;
   ok(engine.name.contains(basename),
      "Sanity check: current engine: engine.name=" + engine.name +
      " basename=" + basename);
 
   // gSearch.currentEngineName
   is(gSearch().currentEngineName, engine.name,
      "currentEngineName: " + engine.name);
-
-  let expectedLogoPrefix = window.devicePixelRatio >= 2 ? logoPrefix2x : logoPrefix1x;
-
-  // Check that the right logo is set.
-  let logo = logoImg();
-  if (expectedLogoPrefix) {
-    let objectURL = logo.style.backgroundImage.match(/^url\("([^"]*)"\)$/)[1];
-    ok(objectURL, "ObjectURL should be there.");
-
-    let blob = yield objectURLToBlob(objectURL);
-    let base64 = yield blobToBase64(blob);
-
-    ok(base64.startsWith(expectedLogoPrefix), "Checking image prefix.");
-
-    logo.click();
-    let panel = searchPanel();
-    yield promisePanelShown(panel);
-
-    panel.hidePopup();
-    for (let engineBox of panel.childNodes) {
-      let engineName = engineBox.getAttribute("engine");
-      if (engineName == engine.name) {
-        is(engineBox.getAttribute("selected"), "true",
-           "Engine box's selected attribute should be true for " +
-           "selected engine: " + engineName);
-      }
-      else {
-        ok(!engineBox.hasAttribute("selected"),
-           "Engine box's selected attribute should be absent for " +
-           "non-selected engine: " + engineName);
-      }
-    }
-  }
-  else {
-    is(logo.style.backgroundImage, "", "backgroundImage should be empty");
-  }
 });
 
 function promisePanelShown(panel) {
   let deferred = Promise.defer();
   info("Waiting for popupshown");
   panel.addEventListener("popupshown", function onEvent() {
     panel.removeEventListener("popupshown", onEvent);
     is(panel.state, "open", "Panel state");
@@ -394,41 +329,16 @@ function promiseClick(node) {
   let win = getContentWindow();
   SimpleTest.waitForFocus(() => {
     EventUtils.synthesizeMouseAtCenter(node, {}, win);
     deferred.resolve();
   }, win);
   return deferred.promise;
 }
 
-function promiseManagerOpen() {
-  info("Waiting for the search manager window to open...");
-  let deferred = Promise.defer();
-  let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-                   getService(Ci.nsIWindowWatcher);
-  winWatcher.registerNotification(function onWin(subj, topic, data) {
-    if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
-      subj.addEventListener("load", function onLoad() {
-        subj.removeEventListener("load", onLoad);
-        if (subj.document.documentURI ==
-            "chrome://browser/content/search/engineManager.xul") {
-          winWatcher.unregisterNotification(onWin);
-          ok(true, "Observed search manager window opened");
-          is(subj.opener, gWindow,
-             "Search engine manager opener should be the chrome browser " +
-             "window containing the newtab page");
-          subj.close();
-          deferred.resolve();
-        }
-      });
-    }
-  });
-  return deferred.promise;
-}
-
 function searchPanel() {
   return $("panel");
 }
 
 function logoImg() {
   return $("logo");
 }
 
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -624,16 +624,22 @@
 
       <method name="selectEngine">
         <body><![CDATA[
           // Override this method to avoid accidentally changing the default
           // engine using the keyboard shortcuts of the old UI.
         ]]></body>
       </method>
 
+      <method name="inputChanged">
+        <body><![CDATA[
+          this.updateGoButtonVisibility();
+        ]]></body>
+      </method>
+
       <method name="updateGoButtonVisibility">
         <body><![CDATA[
           document.getAnonymousElementByAttribute(this, "anonid",
                                                   "search-go-button")
                   .hidden = !this._textbox.value;
         ]]></body>
       </method>
 
@@ -652,18 +658,18 @@
           }
           else if (aShowOnlySettingsIfEmpty) {
             this.setAttribute("showonlysettings", "true");
           }
         ]]></body>
       </method>
     </implementation>
     <handlers>
-      <handler event="input" action="this.updateGoButtonVisibility();"/>
-      <handler event="drop" action="this.updateGoButtonVisibility();"/>
+      <handler event="input" action="this.inputChanged();"/>
+      <handler event="drop" action="this.inputChanged();"/>
       <handler event="focus">
       <![CDATA[
         if (this._textbox.value)
           this.openSuggestionsPanel();
       ]]></handler>
 
       <handler event="click">
       <![CDATA[
--- a/browser/devtools/app-manager/content/connection-footer.js
+++ b/browser/devtools/app-manager/content/connection-footer.js
@@ -132,17 +132,17 @@ let UI = {
   installSimulator: function() {
     let url = "https://developer.mozilla.org/docs/Mozilla/Firefox_OS/Using_the_App_Manager#Simulator";
     window.open(url);
   },
 
   startSimulator: function(version) {
     this._portBeforeSimulatorStarted = this.connection.port;
     let port = ConnectionManager.getFreeTCPPort();
-    let simulator = Simulator.getByVersion(version);
+    let simulator = Simulator.getByName(version);
     if (!simulator) {
       this.connection.log("Error: can't find simulator: " + version);
       return;
     }
     if (!simulator.launch) {
       this.connection.log("Error: invalid simulator: " + version);
       return;
     }
--- a/browser/devtools/app-manager/simulators-store.js
+++ b/browser/devtools/app-manager/simulators-store.js
@@ -4,21 +4,21 @@
 
 const {Cu} = require("chrome");
 const ObservableObject = require("devtools/shared/observable-object");
 const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 
 let store = new ObservableObject({versions:[]});
 
 function feedStore() {
-  store.object.versions = Simulator.availableVersions().map(v => {
-    let simulator = Simulator.getByVersion(v);
+  store.object.versions = Simulator.availableNames().map(name => {
+    let simulator = Simulator.getByName(name);
     return {
-      version: v,
-      label: simulator.appinfo.label
+      version: name,
+      label: simulator ? name : "Unknown"
     }
   });
 }
 
 Simulator.on("register", feedStore);
 Simulator.on("unregister", feedStore);
 feedStore();
 
--- a/browser/devtools/framework/ToolboxProcess.jsm
+++ b/browser/devtools/framework/ToolboxProcess.jsm
@@ -154,21 +154,24 @@ BrowserToolboxProcess.prototype = {
   _initProfile: function() {
     dumpn("Initializing the chrome toolbox user profile.");
 
     let debuggingProfileDir = Services.dirsvc.get("ProfLD", Ci.nsIFile);
     debuggingProfileDir.append(CHROME_DEBUGGER_PROFILE_NAME);
     try {
       debuggingProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
     } catch (ex) {
-      if (ex.result !== Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
+      // Don't re-copy over the prefs again if this profile already exists
+      if (ex.result === Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
+        this._dbgProfilePath = debuggingProfileDir.path;
+      } else {
         dumpn("Error trying to create a profile directory, failing.");
         dumpn("Error: " + (ex.message || ex));
-        return;
       }
+      return;
     }
 
     this._dbgProfilePath = debuggingProfileDir.path;
 
     // We would like to copy prefs into this new profile...
     let prefsFile = debuggingProfileDir.clone();
     prefsFile.append("prefs.js");
     // ... but unfortunately, when we run tests, it seems the starting profile
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -66,19 +66,31 @@ function onCloseCommand(event) {
 function openToolbox(form) {
   let options = {
     form: form,
     client: gClient,
     chrome: true
   };
   devtools.TargetFactory.forRemoteTab(options).then(target => {
     let frame = document.getElementById("toolbox-iframe");
+    let selectedTool = "jsdebugger";
+
+    try {
+      // Remember the last panel that was used inside of this profile.
+      selectedTool = Services.prefs.getCharPref("devtools.toolbox.selectedTool");
+    } catch(e) {}
+
+    try {
+      // But if we are testing, then it should always open the debugger panel.
+      selectedTool = Services.prefs.getCharPref("devtools.browsertoolbox.panel");
+    } catch(e) {}
+
     let options = { customIframe: frame };
     gDevTools.showToolbox(target,
-                          "jsdebugger",
+                          selectedTool,
                           devtools.Toolbox.HostType.CUSTOM,
                           options)
              .then(onNewToolbox);
   });
 }
 
 function onNewToolbox(toolbox) {
    gToolbox = toolbox;
--- a/browser/devtools/projecteditor/lib/editors.js
+++ b/browser/devtools/projecteditor/lib/editors.js
@@ -199,17 +199,17 @@ var TextEditor = Class({
    * @param Resource resource
    *        The single file / item that is being dealt with (see stores/base)
    * @returns Promise
    *          A promise that is resolved once the text editor has loaded the
    *          contents of the resource.
    */
   load: function(resource) {
     // Wait for the editor.appendTo and resource.load before proceeding.
-    // They can run  in parallel.
+    // They can run in parallel.
     return promise.all([
       resource.load(),
       this.appended
     ]).then(([resourceContents])=> {
       if (!this.editor) {
         return;
       }
       this._savedResourceContents = resourceContents;
@@ -240,17 +240,19 @@ var TextEditor = Class({
   /**
    * Give focus to the code editor.
    *
    * @returns Promise
    *          A promise that is resolved once the editor has been focused.
    */
   focus: function() {
     return this.appended.then(() => {
-      this.editor.focus();
+      if (this.editor) {
+        this.editor.focus();
+      }
     });
   }
 });
 
 /**
  * Wrapper for TextEditor using JavaScript syntax highlighting.
  */
 function JSEditor(host) {
--- a/browser/devtools/webide/modules/addons.js
+++ b/browser/devtools/webide/modules/addons.js
@@ -1,18 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {Cu} = require("chrome");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm");
 const {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm");
 const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
-const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
-const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {GetAddonsJSON} = require("devtools/webide/remote-resources");
 
 let SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
 let ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
 let ADAPTERS_LINK = Services.prefs.getCharPref("devtools.webide.adaptersAddonURL");
 let SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
 let ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -200,18 +200,18 @@ let SimulatorScanner = {
   },
 
   _emitUpdated() {
     this.emit("runtime-list-updated");
   },
 
   _updateRuntimes() {
     this._runtimes = [];
-    for (let version of Simulator.availableVersions()) {
-      this._runtimes.push(new SimulatorRuntime(version));
+    for (let name of Simulator.availableNames()) {
+      this._runtimes.push(new SimulatorRuntime(name));
     }
     this._emitUpdated();
   },
 
   scan() {
     return promise.resolve();
   },
 
@@ -458,45 +458,38 @@ WiFiRuntime.prototype = {
   get name() {
     return this.deviceName;
   },
 };
 
 // For testing use only
 exports._WiFiRuntime = WiFiRuntime;
 
-function SimulatorRuntime(version) {
-  this.version = version;
+function SimulatorRuntime(name) {
+  this.name = name;
 }
 
 SimulatorRuntime.prototype = {
   type: RuntimeTypes.SIMULATOR,
   connect: function(connection) {
     let port = ConnectionManager.getFreeTCPPort();
-    let simulator = Simulator.getByVersion(this.version);
+    let simulator = Simulator.getByName(this.name);
     if (!simulator || !simulator.launch) {
       return promise.reject("Can't find simulator: " + this.name);
     }
     return simulator.launch({port: port}).then(() => {
       connection.host = "localhost";
       connection.port = port;
       connection.keepConnecting = true;
       connection.once(Connection.Events.DISCONNECTED, simulator.close);
       connection.connect();
     });
   },
   get id() {
-    return this.version;
-  },
-  get name() {
-    let simulator = Simulator.getByVersion(this.version);
-    if (!simulator) {
-      return "Unknown";
-    }
-    return Simulator.getByVersion(this.version).appinfo.label;
+    return this.name;
   },
 };
 
 // For testing use only
 exports._SimulatorRuntime = SimulatorRuntime;
 
 let gLocalRuntime = {
   type: RuntimeTypes.LOCAL,
--- a/browser/devtools/webide/test/test_addons.html
+++ b/browser/devtools/webide/test/test_addons.html
@@ -23,47 +23,51 @@
         const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 
         let adbAddonsInstalled = promise.defer();
         Devices.on("addon-status-updated", function onUpdate1() {
           Devices.off("addon-status-updated", onUpdate1);
           adbAddonsInstalled.resolve();
         });
 
-        function onSimulatorInstalled(version) {
+        function getVersion(name) {
+          return name.match(/(\d+\.\d+)/)[0];
+        }
+
+        function onSimulatorInstalled(name) {
           let deferred = promise.defer();
           Simulator.on("register", function onUpdate() {
-            if (Simulator.getByVersion(version)) {
+            if (Simulator.getByName(name)) {
               Simulator.off("register", onUpdate);
               nextTick().then(deferred.resolve);
             }
           });
           return deferred.promise;
         }
 
-        function installSimulatorFromUI(doc, version) {
-          let li = doc.querySelector('[addon="simulator-' + version + '"]');
+        function installSimulatorFromUI(doc, name) {
+          let li = doc.querySelector('[addon="simulator-' + getVersion(name) + '"]');
           li.querySelector(".install-button").click();
-          return onSimulatorInstalled(version);
+          return onSimulatorInstalled(name);
         }
 
-        function uninstallSimulatorFromUI(doc, version) {
+        function uninstallSimulatorFromUI(doc, name) {
           let deferred = promise.defer();
           Simulator.on("unregister", function onUpdate() {
             nextTick().then(() => {
-              let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + version + '"]');
+              let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + getVersion(name) + '"]');
               if (li) {
                 Simulator.off("unregister", onUpdate);
                 deferred.resolve();
               } else {
                 deferred.reject("Can't find item");
               }
             })
           });
-          let li = doc.querySelector('[status="installed"][addon="simulator-' + version + '"]');
+          let li = doc.querySelector('[status="installed"][addon="simulator-' + getVersion(name) + '"]');
           li.querySelector(".uninstall-button").click();
           return deferred.promise;
         }
 
         function uninstallADBFromUI(doc) {
           let deferred = promise.defer();
           Devices.on("addon-status-updated", function onUpdate() {
             nextTick().then(() => {
@@ -95,17 +99,17 @@
 
           let addons = yield GetAvailableAddons();
 
           is(addons.simulators.length, 3, "3 simulator addons to install");
 
           let sim10 = addons.simulators.filter(a => a.version == "1.0")[0];
           sim10.install();
 
-          yield onSimulatorInstalled("1.0");
+          yield onSimulatorInstalled("Firefox OS 1.0");
 
           win.Cmds.showAddons();
 
           let frame = win.document.querySelector("#deck-panel-addons");
           let addonDoc = frame.contentWindow.document;
           let lis;
 
           lis = addonDoc.querySelectorAll("li");
@@ -114,36 +118,36 @@
           lis = addonDoc.querySelectorAll('li[status="installed"]');
           is(lis.length, 3, "3 addons installed");
 
           lis = addonDoc.querySelectorAll('li[status="uninstalled"]');
           is(lis.length, 2, "2 addons uninstalled");
 
           info("Uninstalling Simulator 2.0");
 
-          yield installSimulatorFromUI(addonDoc, "2.0");
+          yield installSimulatorFromUI(addonDoc, "Firefox OS 2.0");
 
           info("Uninstalling Simulator 3.0");
 
-          yield installSimulatorFromUI(addonDoc, "3.0");
+          yield installSimulatorFromUI(addonDoc, "Firefox OS 3.0");
 
           yield nextTick();
 
           let panelNode = win.document.querySelector("#runtime-panel");
           let items;
 
           items = panelNode.querySelectorAll(".runtime-panel-item-usb");
           is(items.length, 1, "Found one runtime button");
 
           items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
           is(items.length, 3, "Found 3 simulators button");
 
-          yield uninstallSimulatorFromUI(addonDoc, "1.0");
-          yield uninstallSimulatorFromUI(addonDoc, "2.0");
-          yield uninstallSimulatorFromUI(addonDoc, "3.0");
+          yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 1.0");
+          yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 2.0");
+          yield uninstallSimulatorFromUI(addonDoc, "Firefox OS 3.0");
 
           items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
           is(items.length, 0, "No simulator listed");
 
           let w = addonDoc.querySelector(".warning");
           let display = addonDoc.defaultView.getComputedStyle(w).display
           is(display, "none", "Warning about missing ADB hidden");
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -165,16 +165,17 @@
 #endif
 @RESPATH@/components/appshell.xpt
 @RESPATH@/components/appstartup.xpt
 @RESPATH@/components/autocomplete.xpt
 @RESPATH@/components/autoconfig.xpt
 @RESPATH@/components/browser-element.xpt
 @RESPATH@/browser/components/browsercompsbase.xpt
 @RESPATH@/browser/components/browser-feeds.xpt
+@RESPATH@/browser/components/browsermodules.manifest
 @RESPATH@/components/caps.xpt
 @RESPATH@/components/chrome.xpt
 @RESPATH@/components/commandhandler.xpt
 @RESPATH@/components/commandlines.xpt
 @RESPATH@/components/composer.xpt
 @RESPATH@/components/content_events.xpt
 @RESPATH@/components/content_html.xpt
 @RESPATH@/components/content_geckomediaplugins.xpt
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -92,16 +92,17 @@ let AboutHome = {
     "AboutHome:Bookmarks",
     "AboutHome:History",
     "AboutHome:Apps",
     "AboutHome:Addons",
     "AboutHome:Sync",
     "AboutHome:Settings",
     "AboutHome:RequestUpdate",
     "AboutHome:Search",
+    "AboutHome:OpenSearchPanel",
   ],
 
   init: function() {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
 
     for (let msg of this.MESSAGES) {
       mm.addMessageListener(msg, this);
     }
@@ -198,16 +199,28 @@ let AboutHome = {
           window.loadURI(submission.uri.spec, null, submission.postData);
 
           // Used for testing
           let mm = aMessage.target.messageManager;
           mm.sendAsyncMessage("AboutHome:SearchTriggered", aMessage.data.searchData);
         });
 
         break;
+
+      case "AboutHome:OpenSearchPanel":
+        let panel = window.document.getElementById("abouthome-search-panel");
+        let anchor = aMessage.objects.anchor;
+        panel.hidden = false;
+        panel.openPopup(anchor);
+        anchor.setAttribute("active", "true");
+        panel.addEventListener("popuphidden", function onHidden() {
+          panel.removeEventListener("popuphidden", onHidden);
+          anchor.removeAttribute("active");
+        });
+        break;
     }
   },
 
   // Send all the chrome-privileged data needed by about:home. This
   // gets re-sent when the search engine changes.
   sendAboutHomeData: function(target) {
     let wrapper = {};
     Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm",
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -215,16 +215,22 @@ this.ContentSearch = {
 
   _onMessageSetCurrentEngine: function (msg, data) {
     Services.search.currentEngine = Services.search.getEngineByName(data);
     return Promise.resolve();
   },
 
   _onMessageManageEngines: function (msg, data) {
     let browserWin = msg.target.ownerDocument.defaultView;
+
+    if (Services.prefs.getBoolPref("browser.search.showOneOffButtons")) {
+      browserWin.openPreferences("paneSearch");
+      return Promise.resolve();
+    }
+
     let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
              getService(Components.interfaces.nsIWindowMediator);
     let window = wm.getMostRecentWindow("Browser:SearchManager");
 
     if (window) {
       window.focus()
     }
     else {
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -1,15 +1,15 @@
 // 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/.
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["UITour"];
+this.EXPORTED_SYMBOLS = ["UITour", "UITourMetricsProvider"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
@@ -18,17 +18,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ResetProfile",
   "resource://gre/modules/ResetProfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
-
+XPCOMUtils.defineLazyModuleGetter(this, "Metrics",
+  "resource://gre/modules/Metrics.jsm");
 
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL      = "browser.uitour.loglevel";
 const PREF_SEENPAGEIDS    = "browser.uitour.seenPageIDs";
 const MAX_BUTTONS         = 4;
 
 const BUCKET_NAME         = "UITour";
 const BUCKET_TIMESTEPS    = [
@@ -516,16 +517,74 @@ this.UITour = {
       case "addNavBarWidget": {
         // Add a widget to the toolbar
         let targetPromise = this.getTarget(window, data.name);
         targetPromise.then(target => {
           this.addNavBarWidget(target, messageManager, data.callbackID);
         }).catch(log.error);
         break;
       }
+
+      case "setDefaultSearchEngine": {
+        let enginePromise = this.selectSearchEngine(data.identifier);
+        enginePromise.catch(Cu.reportError);
+        break;
+      }
+
+      case "setTreatmentTag": {
+        let name = data.name;
+        let value = data.value;
+        let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+        string.data = value;
+        Services.prefs.setComplexValue("browser.uitour.treatment." + name,
+                                       Ci.nsISupportsString, string);
+        UITourHealthReport.recordTreatmentTag(name, value);
+        break;
+      }
+
+      case "getTreatmentTag": {
+        let name = data.name;
+        let value;
+        try {
+          value = Services.prefs.getComplexValue("browser.uitour.treatment." + name,
+                                                 Ci.nsISupportsString).data;
+        } catch (ex) {}
+        this.sendPageCallback(messageManager, data.callbackID, { value: value });
+        break;
+      }
+
+      case "setSearchTerm": {
+        let targetPromise = this.getTarget(window, "search");
+        targetPromise.then(target => {
+          let searchbar = target.node;
+          searchbar.value = data.term;
+          searchbar.inputChanged();
+        }).then(null, Cu.reportError);
+        break;
+      }
+
+      case "openSearchPanel": {
+        let targetPromise = this.getTarget(window, "search");
+        targetPromise.then(target => {
+          let searchbar = target.node;
+
+          if (searchbar.textbox.open) {
+            this.sendPageCallback(messageManager, data.callbackID);
+          } else {
+            let onPopupShown = () => {
+              searchbar.textbox.popup.removeEventListener("popupshown", onPopupShown);
+              this.sendPageCallback(messageManager, data.callbackID);
+            };
+
+            searchbar.textbox.popup.addEventListener("popupshown", onPopupShown);
+            searchbar.openSuggestionsPanel();
+          }
+        }).then(null, Cu.reportError);
+        break;
+      }
     }
 
     if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
       if (!this.originTabs.has(window)) {
         this.originTabs.set(window, new Set());
       }
 
       this.originTabs.get(window).add(tab);
@@ -1100,19 +1159,24 @@ this.UITour = {
         tooltipClose.removeEventListener("command", closeButtonCallback);
         if (aOptions.targetCallbackID && aAnchor.removeTargetListener) {
           aAnchor.removeTargetListener(document, targetCallback);
         }
       });
 
       tooltip.setAttribute("targetName", aAnchor.targetName);
       tooltip.hidden = false;
+      let xOffset = 0, yOffset = 0;
       let alignment = "bottomcenter topright";
+      if (aAnchor.targetName == "search") {
+        alignment = "after_start";
+        xOffset = 18;
+      }
       this._addAnnotationPanelMutationObserver(tooltip);
-      tooltip.openPopup(aAnchorEl, alignment);
+      tooltip.openPopup(aAnchorEl, alignment, xOffset, yOffset);
       if (tooltip.state == "closed") {
         document.defaultView.addEventListener("endmodalstate", function endModalStateHandler() {
           document.defaultView.removeEventListener("endmodalstate", endModalStateHandler);
           tooltip.openPopup(aAnchorEl, alignment);
         }, false);
       }
     }
 
@@ -1285,16 +1349,29 @@ this.UITour = {
         });
         break;
       case "appinfo":
         let props = ["defaultUpdateChannel", "version"];
         let appinfo = {};
         props.forEach(property => appinfo[property] = Services.appinfo[property]);
         this.sendPageCallback(aMessageManager, aCallbackID, appinfo);
         break;
+      case "selectedSearchEngine":
+        Services.search.init(rv => {
+          let engine;
+          if (Components.isSuccessCode(rv)) {
+            engine = Services.search.defaultEngine;
+          } else {
+            engine = { identifier: "" };
+          }
+          this.sendPageCallback(aMessageManager, aCallbackID, {
+            searchEngineIdentifier: engine.identifier
+          });
+        });
+        break;
       default:
         log.error("getConfiguration: Unknown configuration requested: " + aConfiguration);
         break;
     }
   },
 
   getAvailableTargets: function(aMessageManager, aChromeWindow, aCallbackID) {
     Task.spawn(function*() {
@@ -1392,16 +1469,36 @@ this.UITour = {
     for (let mutation of aMutations) {
       // Remove both attributes at once and ignore remaining mutations to be proccessed.
       mutation.target.removeAttribute("width");
       mutation.target.removeAttribute("height");
       return;
     }
   },
 
+  selectSearchEngine(aID) {
+    return new Promise((resolve, reject) => {
+      Services.search.init((rv) => {
+        if (!Components.isSuccessCode(rv)) {
+          reject("selectSearchEngine: search service init failed: " + rv);
+          return;
+        }
+
+        let engines = Services.search.getVisibleEngines();
+        for (let engine of engines) {
+          if (engine.identifier == aID) {
+            Services.search.defaultEngine = engine;
+            return resolve();
+          }
+        }
+        reject("selectSearchEngine could not find engine with given ID");
+      });
+    });
+  },
+
   getAvailableSearchEngineTargets(aWindow) {
     return new Promise(resolve => {
       this.getTarget(aWindow, "search").then(searchTarget => {
         if (!searchTarget.node || this.targetIsInAppMenu(searchTarget))
           return resolve([]);
 
         Services.search.init(() => {
           let engines = Services.search.getVisibleEngines();
@@ -1443,8 +1540,110 @@ this.UITour = {
       }.bind(this)).catch(() => {
         reject("Search engine not available");
       });
     });
   }
 };
 
 this.UITour.init();
+
+/**
+ * UITour Health Report
+ */
+const DAILY_DISCRETE_TEXT_FIELD = Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT;
+
+/**
+ * Public API to be called by the UITour code
+ */
+const UITourHealthReport = {
+  recordTreatmentTag: function(tag, value) {
+#ifdef MOZ_SERVICES_HEALTHREPORT
+    Task.spawn(function*() {
+      let reporter = Cc["@mozilla.org/datareporting/service;1"]
+                       .getService()
+                       .wrappedJSObject
+                       .healthReporter;
+
+      // This can happen if the FHR component of the data reporting service is
+      // disabled. This is controlled by a pref that most will never use.
+      if (!reporter) {
+        return;
+      }
+
+      yield reporter.onInit();
+
+      // Get the UITourMetricsProvider instance from the Health Reporter
+      reporter.getProvider("org.mozilla.uitour").recordTreatmentTag(tag, value);
+    });
+#endif
+  }
+};
+
+this.UITourMetricsProvider = function() {
+  Metrics.Provider.call(this);
+}
+
+UITourMetricsProvider.prototype = Object.freeze({
+  __proto__: Metrics.Provider.prototype,
+
+  name: "org.mozilla.uitour",
+
+  measurementTypes: [
+    UITourTreatmentMeasurement1,
+  ],
+
+  recordTreatmentTag: function(tag, value) {
+    let m = this.getMeasurement(UITourTreatmentMeasurement1.prototype.name,
+                                UITourTreatmentMeasurement1.prototype.version);
+    let field = tag;
+
+    if (this.storage.hasFieldFromMeasurement(m.id, field,
+                                             DAILY_DISCRETE_TEXT_FIELD)) {
+      let fieldID = this.storage.fieldIDFromMeasurement(m.id, field);
+      return this.enqueueStorageOperation(function recordKnownField() {
+        return this.storage.addDailyDiscreteTextFromFieldID(fieldID, value);
+      }.bind(this));
+    }
+
+    // Otherwise, we first need to create the field.
+    return this.enqueueStorageOperation(function recordField() {
+      // This function has to return a promise.
+      return Task.spawn(function () {
+        let fieldID = yield this.storage.registerField(m.id, field,
+                                                       DAILY_DISCRETE_TEXT_FIELD);
+        yield this.storage.addDailyDiscreteTextFromFieldID(fieldID, value);
+      }.bind(this));
+    }.bind(this));
+  },
+});
+
+function UITourTreatmentMeasurement1() {
+  Metrics.Measurement.call(this);
+
+  this._serializers = {};
+  this._serializers[this.SERIALIZE_JSON] = {
+    //singular: We don't need a singular serializer because we have none of this data
+    daily: this._serializeJSONDaily.bind(this)
+  };
+
+}
+
+UITourTreatmentMeasurement1.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+
+  name: "treatment",
+  version: 1,
+
+  // our fields are dynamic
+  fields: { },
+
+  // We need a custom serializer because the default one doesn't accept unknown fields
+  _serializeJSONDaily: function(data) {
+    let result = {_v: this.version };
+
+    for (let [field, data] of data) {
+      result[field] = data;
+    }
+
+    return result;
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/modules/browsermodules.manifest
@@ -0,0 +1,3 @@
+#ifdef MOZ_SERVICES_HEALTHREPORT
+category healthreport-js-provider-default UITourMetricsProvider resource:///modules/UITour.jsm
+#endif
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -50,10 +50,14 @@ if CONFIG['NIGHTLY_BUILD']:
 EXTRA_PP_JS_MODULES += [
     'AboutHome.jsm',
     'PluginContent.jsm',
     'RecentWindow.jsm',
     'UITour.jsm',
     'webrtcUI.jsm',
 ]
 
+EXTRA_PP_COMPONENTS += [
+    'browsermodules.manifest',
+]
+
 if CONFIG['MOZILLA_OFFICIAL']:
     DEFINES['MOZILLA_OFFICIAL'] = 1
--- a/browser/modules/test/browser_ContentSearch.js
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -65,44 +65,16 @@ add_task(function* SetCurrentEngine() {
   Services.search.currentEngine = oldCurrentEngine;
   msg = yield waitForTestMsg("CurrentEngine");
   checkMsg(msg, {
     type: "CurrentEngine",
     data: yield currentEngineObj(oldCurrentEngine),
   });
 });
 
-add_task(function* ManageEngines() {
-  yield addTab();
-  gMsgMan.sendAsyncMessage(TEST_MSG, {
-    type: "ManageEngines",
-  });
-  let deferred = Promise.defer();
-  let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-                   getService(Ci.nsIWindowWatcher);
-  winWatcher.registerNotification(function onOpen(subj, topic, data) {
-    if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
-      subj.addEventListener("load", function onLoad() {
-        subj.removeEventListener("load", onLoad);
-        if (subj.document.documentURI ==
-            "chrome://browser/content/search/engineManager.xul") {
-          winWatcher.unregisterNotification(onOpen);
-          ok(true, "Observed search manager window open");
-          is(subj.opener, window,
-             "Search engine manager opener should be this chrome window");
-          subj.close();
-          deferred.resolve();
-        }
-      });
-    }
-  });
-  info("Waiting for search engine manager window to open...");
-  yield deferred.promise;
-});
-
 add_task(function* modifyEngine() {
   yield addTab();
   let engine = Services.search.currentEngine;
   let oldAlias = engine.alias;
   engine.alias = "ContentSearchTest";
   let msg = yield waitForTestMsg("CurrentState");
   checkMsg(msg, {
     type: "CurrentState",
--- a/browser/modules/test/browser_UITour.js
+++ b/browser/modules/test/browser_UITour.js
@@ -364,16 +364,54 @@ let tests = [
 
           // Cleanup
           CustomizableUI.removeWidgetFromArea("panic-button");
           done();
         });
       });
     });
   },
+  function test_select_search_engine(done) {
+    Services.search.init(rv => {
+      if (!Components.isSuccessCode(rv)) {
+        ok(false, "search service init failed: " + rv);
+        done();
+        return;
+      }
+      let defaultEngine = Services.search.defaultEngine;
+      gContentAPI.getConfiguration("availableTargets", data => {
+        let searchEngines = data.targets.filter(t => t.startsWith("searchEngine-"));
+        let someOtherEngineID = searchEngines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
+        someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
+
+        let observe = function (subject, topic, verb) {
+          info("browser-search-engine-modified: " + verb);
+          if (verb == "engine-current") {
+            is(Services.search.defaultEngine.identifier, someOtherEngineID, "correct engine was switched to");
+            done();
+          }
+        };
+        Services.obs.addObserver(observe, "browser-search-engine-modified", false);
+        registerCleanupFunction(() => {
+          // Clean up
+          Services.obs.removeObserver(observe, "browser-search-engine-modified");
+          Services.search.defaultEngine = defaultEngine;
+        });
+
+        gContentAPI.setDefaultSearchEngine(someOtherEngineID);
+      });
+    });
+  },
+  function test_treatment_tag(done) {
+    gContentAPI.setTreatmentTag("foobar", "baz");
+    gContentAPI.getTreatmentTag("foobar", (data) => {
+      is(data.value, "baz", "set and retrieved treatmentTag");
+      done();
+    });
+  },
 
   // Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
   taskify(function* cleanupMenus() {
     let shownPromise = promisePanelShown(window);
     gContentAPI.showMenu("appMenu");
     yield shownPromise;
   }),
 ];
--- a/browser/modules/test/browser_UITour3.js
+++ b/browser/modules/test/browser_UITour3.js
@@ -3,16 +3,17 @@
 
 "use strict";
 
 let gTestTab;
 let gContentAPI;
 let gContentWindow;
 
 Components.utils.import("resource:///modules/UITour.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
 
 requestLongerTimeout(2);
 
 function test() {
   UITourTest();
 }
 
 let tests = [
@@ -140,9 +141,59 @@ let tests = [
     is(gContentWindow.callbackData.target, "appMenu", "target callback was from the appMenu");
     is(gContentWindow.callbackData.type, "popupshown", "target callback was from the mousedown");
 
     // Cleanup.
     yield hideInfoPromise();
 
     popup.removeAttribute("animate");
   }),
+
+  function test_getConfiguration_selectedSearchEngine(done) {
+    Services.search.init(rv => {
+      ok(Components.isSuccessCode(rv), "Search service initialized");
+      let engine = Services.search.defaultEngine;
+      gContentAPI.getConfiguration("selectedSearchEngine", (data) => {
+        is(data.searchEngineIdentifier, engine.identifier, "Correct engine identifier");
+        done();
+      });
+    });
+  },
+
+  function test_setSearchTerm(done) {
+    const TERM = "UITour Search Term";
+    gContentAPI.setSearchTerm(TERM);
+
+    let searchbar = document.getElementById("searchbar");
+    // The UITour gets to the searchbar element through a promise, so the value setting
+    // only happens after a tick.
+    waitForCondition(() => searchbar.value == TERM, done, "Correct term set");
+  },
+
+  function test_clearSearchTerm(done) {
+    gContentAPI.setSearchTerm("");
+
+    let searchbar = document.getElementById("searchbar");
+    // The UITour gets to the searchbar element through a promise, so the value setting
+    // only happens after a tick.
+    waitForCondition(() => searchbar.value == "", done, "Search term cleared");
+  },
+
+  function test_openSearchPanel(done) {
+    let searchbar = document.getElementById("searchbar");
+
+    // If suggestions are enabled, the panel will attempt to use the network to connect
+    // to the suggestions provider, causing the test suite to fail.
+    Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
+    registerCleanupFunction(() => {
+      Services.prefs.clearUserPref("browser.search.suggest.enabled");
+    });
+
+    ok(!searchbar.textbox.open, "Popup starts as closed");
+    gContentAPI.openSearchPanel(() => {
+      ok(searchbar.textbox.open, "Popup was opened");
+      searchbar.textbox.closePopup();
+      ok(!searchbar.textbox.open, "Popup was closed");
+      done();
+    });
+  },
+
 ];
--- a/browser/modules/test/head.js
+++ b/browser/modules/test/head.js
@@ -1,13 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource:///modules/UITour.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UITour",
+  "resource:///modules/UITour.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 const SINGLE_TRY_TIMEOUT = 100;
 const NUMBER_OF_TRIES = 30;
 
 function waitForConditionPromise(condition, timeoutMsg, tryCount=NUMBER_OF_TRIES) {
   let defer = Promise.defer();
   let tries = 0;
@@ -214,16 +215,17 @@ function UITourTest() {
   Services.prefs.setBoolPref("browser.uitour.enabled", true);
   let testUri = Services.io.newURI("http://example.com", null, null);
   Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
 
   waitForExplicitFinish();
 
   registerCleanupFunction(function() {
     delete window.UITour;
+    delete window.UITourMetricsProvider;
     delete window.gContentWindow;
     delete window.gContentAPI;
     if (gTestTab)
       gBrowser.removeTab(gTestTab);
     delete window.gTestTab;
     Services.prefs.clearUserPref("browser.uitour.enabled", true);
     Services.perms.remove("example.com", "uitour");
   });
--- a/browser/modules/test/uitour.js
+++ b/browser/modules/test/uitour.js
@@ -202,9 +202,41 @@ if (typeof Mozilla == 'undefined') {
 
 	Mozilla.UITour.addNavBarWidget= function(name, callback) {
 		_sendEvent('addNavBarWidget', {
 			name: name,
 			callbackID: _waitForCallback(callback),
 		});
 	};
 
+	Mozilla.UITour.setDefaultSearchEngine = function(identifier) {
+		_sendEvent('setDefaultSearchEngine', {
+			identifier: identifier,
+		});
+	};
+
+	Mozilla.UITour.setTreatmentTag = function(name, value) {
+		_sendEvent('setTreatmentTag', {
+			name: name,
+			value: value
+		});
+	};
+
+	Mozilla.UITour.getTreatmentTag = function(name, callback) {
+		_sendEvent('getTreatmentTag', {
+			name: name,
+			callbackID: _waitForCallback(callback)
+		});
+	};
+
+	Mozilla.UITour.setSearchTerm = function(term) {
+		_sendEvent('setSearchTerm', {
+			term: term
+		});
+	};
+
+	Mozilla.UITour.openSearchPanel = function(callback) {
+		_sendEvent('openSearchPanel', {
+			callbackID: _waitForCallback(callback)
+		});
+	};
+
 })();
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -35,16 +35,18 @@ browser.jar:
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/identity.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https-ev.png
   skin/classic/browser/identity-icons-https-mixed-active.png
   skin/classic/browser/identity-icons-https-mixed-display.png
   skin/classic/browser/Info.png
+  skin/classic/browser/magnifier.png                        (../shared/magnifier.png)
+  skin/classic/browser/magnifier@2x.png                     (../shared/magnifier@2x.png)
   skin/classic/browser/mask.png                             (../shared/mask.png)
   skin/classic/browser/mask@2x.png                          (../shared/mask@2x.png)
   skin/classic/browser/menuPanel.png
   skin/classic/browser/menuPanel-customize.png
   skin/classic/browser/menuPanel-exit.png
   skin/classic/browser/menuPanel-help.png
   skin/classic/browser/menuPanel-small.png
   skin/classic/browser/bad-content-blocked-16.png
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -53,16 +53,18 @@ browser.jar:
   skin/classic/browser/KUI-background.png
   skin/classic/browser/subtle-pattern.png
   skin/classic/browser/menu-back.png
   skin/classic/browser/menu-forward.png
   skin/classic/browser/notification-16.png
   skin/classic/browser/notification-16@2x.png
   skin/classic/browser/notification-64.png
   skin/classic/browser/notification-64@2x.png
+  skin/classic/browser/magnifier.png                        (../shared/magnifier.png)
+  skin/classic/browser/magnifier@2x.png                     (../shared/magnifier@2x.png)
   skin/classic/browser/mask.png                             (../shared/mask.png)
   skin/classic/browser/mask@2x.png                          (../shared/mask@2x.png)
   skin/classic/browser/menuPanel.png
   skin/classic/browser/menuPanel@2x.png
   skin/classic/browser/menuPanel-customize.png
   skin/classic/browser/menuPanel-customize@2x.png
   skin/classic/browser/menuPanel-exit.png
   skin/classic/browser/menuPanel-exit@2x.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bcce4ad2b1c9fa5c6e4c85dd2e6461cbba46aef3
GIT binary patch
literal 371
zc$@)q0gV2MP)<h;3K|Lk000e1NJLTq000^Q000^Y1ONa414D#G0003xNkl<ZcmbW$
zze)o^5XbSg2|<EU(87rK24bt2%3$Z8_c2DkBC#>1QV}fG&KF2!Owd9fLNp+!f<;0C
zsZLl1Ovc&byeSUmw;z_d-E+SW5fz$r81g}zJtCZ4@*>L#Lz1x?QX?Bib3Bun^~9`=
zGCatVcMe!*nG#iE>9xpMs3!3_lOs`%E9qDNqDW@B&=C{d_@vK*M*BW^HAkM}%cr{<
zwR{_#Sw}u?YSi)VQUGoG^hKk-Z!ZPVs!x0NP8$KVEjyVr9R^U{r*|4vd^;v*kQu)1
zueT`YbZMN^t(;RKd?T*McAFNGaAx1Vw!|^VY_Y-)bv{X?x~!$T<(ldLsmeet9dJs|
zVk4dcWtzOvC+40q^Eir{X=GQCu4ayuOi+5}_y=>OXAMVM_DJ55jy1^RegG-j88vO|
R3&{Wg002ovPDHLkV1mWvp#J~>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6aef26a10c78f28a360000c394f462443e142f5f
GIT binary patch
literal 689
zc$@*T0#5yjP)<h;3K|Lk000e1NJLTq001-q001-y1ONa4Z*kLL0007bNkl<ZcmciE
zO-od77=ZEH8BnK1pkKgLP{@@#gFgjIegbu0<jZ*ae+9Yv1V%-RA{Y6xD5zEKjGz}y
zClzW_n_fsQ0-eeVGv>)f!Og?WIrlTe9Iusf-~8~IbB0GM|IkguEGumBl`m|v!c8XW
zLaIq9*AdRMfvZq~h+OSZh6z6568gv(nOdNBp5cr=p}ksYkRmRiT?VRwM%c#%w$D%~
zbh->vqQFJ+3?CM8g_mWi{t&1G|ImIgO(){j!*ve4$sO9PP?lwH`2ia7u~l#SF&P__
z_ZBaaMPh1V-rG25fpXq^a}`OffjOLNi(E4FKTh?KEMlnH`|g-XhSt^iPdX4|z5G_2
z>q$^IPBo3#^FFDLcqr2E4V^(ypN1HVhi2928WPxhwLKFLt%e_*YC(U=#zXqi9uJ15
z)b_i0=(`#n4u&T6cX>Rtr$$GDp-4maqss?F7wkv3qDC(VL$~z9V1K+{1Tqb5+aK>q
zoa%TGbPA^$jfc8$s%t^e9h|B?9un5n@GpAI%zu>B=5r(oioEM#qlr1nE6c%YD91KV
zHAh1t)5txXYLlj9NEpZISfDxKzq#*ioJA~9h9z%tgWj0W=CwDyN2?7Ij<Mq{A282J
zvfhCWa+gxsi+_s^68g*DQ$Mr76nP@xHoG`4Go^nS(v&SbCFlm&trzA0p^Zm4W8Xq3
z!7sxoTZzaA&d^H1PF1=bPEe?bEHnQ1^=z?ILghLcV}>Q(vUAuQo^pdx@AhV<gqpHb
zLQ0g75+$TW2`N!RN|cZiC8R_N=}?P88dQ-JLj5SkAVU2p#ncH>RFOWUPLQG&|1roP
X(~)SO`(MEl00000NkvXXu0mjff=N5(
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -40,16 +40,18 @@ browser.jar:
         skin/classic/browser/identity-icons-generic.png
         skin/classic/browser/identity-icons-https.png
         skin/classic/browser/identity-icons-https-ev.png
         skin/classic/browser/identity-icons-https-mixed-active.png
         skin/classic/browser/identity-icons-https-mixed-display.png
         skin/classic/browser/keyhole-forward-mask.svg
         skin/classic/browser/KUI-background.png
         skin/classic/browser/livemark-folder.png
+        skin/classic/browser/magnifier.png                          (../shared/magnifier.png)
+        skin/classic/browser/magnifier@2x.png                       (../shared/magnifier@2x.png)
         skin/classic/browser/mask.png                               (../shared/mask.png)
         skin/classic/browser/mask@2x.png                            (../shared/mask@2x.png)
         skin/classic/browser/menu-back.png
         skin/classic/browser/menu-forward.png
         skin/classic/browser/menuPanel.png
         skin/classic/browser/menuPanel-customize.png
         skin/classic/browser/menuPanel-exit.png
         skin/classic/browser/menuPanel-help.png
@@ -478,16 +480,18 @@ browser.jar:
         skin/classic/aero/browser/identity-icons-generic.png
         skin/classic/aero/browser/identity-icons-https.png
         skin/classic/aero/browser/identity-icons-https-ev.png
         skin/classic/aero/browser/identity-icons-https-mixed-active.png
         skin/classic/aero/browser/identity-icons-https-mixed-display.png
         skin/classic/aero/browser/keyhole-forward-mask.svg
         skin/classic/aero/browser/KUI-background.png
         skin/classic/aero/browser/livemark-folder.png                (livemark-folder-aero.png)
+        skin/classic/aero/browser/magnifier.png                      (../shared/magnifier.png)
+        skin/classic/aero/browser/magnifier@2x.png                   (../shared/magnifier@2x.png)
         skin/classic/aero/browser/mask.png                           (../shared/mask.png)
         skin/classic/aero/browser/mask@2x.png                        (../shared/mask@2x.png)
         skin/classic/aero/browser/menu-back.png                      (menu-back-aero.png)
         skin/classic/aero/browser/menu-forward.png                   (menu-forward-aero.png)
         skin/classic/aero/browser/menuPanel.png
         skin/classic/aero/browser/menuPanel-aero.png
         skin/classic/aero/browser/menuPanel-customize.png
         skin/classic/aero/browser/menuPanel-exit.png
--- a/dom/canvas/WebGL2ContextTextures.cpp
+++ b/dom/canvas/WebGL2ContextTextures.cpp
@@ -455,19 +455,36 @@ WebGL2Context::CompressedTexSubImage3D(G
 {
     MOZ_CRASH("Not Implemented.");
 }
 
 JS::Value
 WebGL2Context::GetTexParameterInternal(const TexTarget& target, GLenum pname)
 {
     switch (pname) {
+        case LOCAL_GL_TEXTURE_BASE_LEVEL:
+        case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+        case LOCAL_GL_TEXTURE_COMPARE_MODE:
         case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
-        case LOCAL_GL_TEXTURE_BASE_LEVEL:
+        case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS:
         case LOCAL_GL_TEXTURE_MAX_LEVEL:
+        case LOCAL_GL_TEXTURE_SWIZZLE_A:
+        case LOCAL_GL_TEXTURE_SWIZZLE_B:
+        case LOCAL_GL_TEXTURE_SWIZZLE_G:
+        case LOCAL_GL_TEXTURE_SWIZZLE_R:
+        case LOCAL_GL_TEXTURE_WRAP_R:
         {
             GLint i = 0;
             gl->fGetTexParameteriv(target.get(), pname, &i);
             return JS::NumberValue(uint32_t(i));
         }
+
+        case LOCAL_GL_TEXTURE_MAX_LOD:
+        case LOCAL_GL_TEXTURE_MIN_LOD:
+        {
+            GLfloat f = 0.0f;
+            gl->fGetTexParameterfv(target.get(), pname, &f);
+            return JS::NumberValue(float(f));
+        }
     }
+
     return WebGLContext::GetTexParameterInternal(target, pname);
 }
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1574,16 +1574,45 @@ void WebGLContext::TexParameter_base(GLe
                 paramValueInvalid = true;
                 break;
             }
             if (pname == LOCAL_GL_TEXTURE_BASE_LEVEL)
                 tex->SetBaseMipmapLevel(intParam);
             else
                 tex->SetMaxMipmapLevel(intParam);
             break;
+
+        case LOCAL_GL_TEXTURE_COMPARE_MODE:
+            if (!IsWebGL2())
+                return ErrorInvalidEnumInfo("texParameter: pname", pname);
+
+            paramValueInvalid = (intParam != LOCAL_GL_NONE &&
+                                 intParam != LOCAL_GL_COMPARE_REF_TO_TEXTURE);
+            break;
+
+        case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+            if (!IsWebGL2())
+                return ErrorInvalidEnumInfo("texParameter: pname", pname);
+
+            switch (intParam) {
+            case LOCAL_GL_LEQUAL:
+            case LOCAL_GL_GEQUAL:
+            case LOCAL_GL_LESS:
+            case LOCAL_GL_GREATER:
+            case LOCAL_GL_EQUAL:
+            case LOCAL_GL_NOTEQUAL:
+            case LOCAL_GL_ALWAYS:
+            case LOCAL_GL_NEVER:
+                paramValueInvalid = false;
+
+            default:
+                paramValueInvalid = true;
+            }
+            break;
+
         case LOCAL_GL_TEXTURE_MIN_FILTER:
             switch (intParam) {
                 case LOCAL_GL_NEAREST:
                 case LOCAL_GL_LINEAR:
                 case LOCAL_GL_NEAREST_MIPMAP_NEAREST:
                 case LOCAL_GL_LINEAR_MIPMAP_NEAREST:
                 case LOCAL_GL_NEAREST_MIPMAP_LINEAR:
                 case LOCAL_GL_LINEAR_MIPMAP_LINEAR:
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -1540,30 +1540,48 @@ IsUniformSetterTypeValid(GLenum setterTy
     switch (uniformType) {
     case LOCAL_GL_BOOL:
     case LOCAL_GL_BOOL_VEC2:
     case LOCAL_GL_BOOL_VEC3:
     case LOCAL_GL_BOOL_VEC4:
         return true; // GLfloat(0.0) sets a bool to false.
 
     case LOCAL_GL_INT:
-    case LOCAL_GL_SAMPLER_2D:
-    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_INT_SAMPLER_2D:
+    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_INT_SAMPLER_3D:
+    case LOCAL_GL_INT_SAMPLER_CUBE:
     case LOCAL_GL_INT_VEC2:
     case LOCAL_GL_INT_VEC3:
     case LOCAL_GL_INT_VEC4:
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
         return setterType == LOCAL_GL_INT;
 
     case LOCAL_GL_FLOAT:
+    case LOCAL_GL_FLOAT_MAT2:
+    case LOCAL_GL_FLOAT_MAT2x3:
+    case LOCAL_GL_FLOAT_MAT2x4:
+    case LOCAL_GL_FLOAT_MAT3:
+    case LOCAL_GL_FLOAT_MAT3x2:
+    case LOCAL_GL_FLOAT_MAT3x4:
+    case LOCAL_GL_FLOAT_MAT4:
+    case LOCAL_GL_FLOAT_MAT4x2:
+    case LOCAL_GL_FLOAT_MAT4x3:
     case LOCAL_GL_FLOAT_VEC2:
     case LOCAL_GL_FLOAT_VEC3:
     case LOCAL_GL_FLOAT_VEC4:
-    case LOCAL_GL_FLOAT_MAT2:
-    case LOCAL_GL_FLOAT_MAT3:
-    case LOCAL_GL_FLOAT_MAT4:
         return setterType == LOCAL_GL_FLOAT;
 
     default:
         MOZ_ASSERT(false); // should never get here
         return false;
     }
 }
 
--- a/dom/canvas/WebGLUniformInfo.h
+++ b/dom/canvas/WebGLUniformInfo.h
@@ -23,27 +23,27 @@ struct WebGLUniformInfo {
         switch (type) {
             case LOCAL_GL_FLOAT:
             case LOCAL_GL_INT:
             case LOCAL_GL_UNSIGNED_INT:
             case LOCAL_GL_BOOL:
             case LOCAL_GL_SAMPLER_2D:
             case LOCAL_GL_SAMPLER_3D:
             case LOCAL_GL_SAMPLER_CUBE:
-            case LOCAL_GL_SAMPLER_2D_SHADOW:
-            case LOCAL_GL_SAMPLER_2D_ARRAY:
-            case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
-            case LOCAL_GL_SAMPLER_CUBE_SHADOW:
-            case LOCAL_GL_INT_SAMPLER_2D:
-            case LOCAL_GL_INT_SAMPLER_3D:
-            case LOCAL_GL_INT_SAMPLER_CUBE:
-            case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
-            case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
-            case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
-            case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+            case LOCAL_GL_SAMPLER_2D_SHADOW:
+            case LOCAL_GL_SAMPLER_2D_ARRAY:
+            case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+            case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+            case LOCAL_GL_INT_SAMPLER_2D:
+            case LOCAL_GL_INT_SAMPLER_3D:
+            case LOCAL_GL_INT_SAMPLER_CUBE:
+            case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+            case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+            case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+            case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
             case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
                 return 1;
             case LOCAL_GL_FLOAT_VEC2:
             case LOCAL_GL_INT_VEC2:
             case LOCAL_GL_UNSIGNED_INT_VEC2:
             case LOCAL_GL_BOOL_VEC2:
                 return 2;
             case LOCAL_GL_FLOAT_VEC3:
@@ -53,26 +53,26 @@ struct WebGLUniformInfo {
                 return 3;
             case LOCAL_GL_FLOAT_VEC4:
             case LOCAL_GL_INT_VEC4:
             case LOCAL_GL_UNSIGNED_INT_VEC4:
             case LOCAL_GL_BOOL_VEC4:
             case LOCAL_GL_FLOAT_MAT2:
                 return 4;
             case LOCAL_GL_FLOAT_MAT2x3:
-            case LOCAL_GL_FLOAT_MAT3x2:
+            case LOCAL_GL_FLOAT_MAT3x2:
                 return 6;
-            case LOCAL_GL_FLOAT_MAT2x4:
-            case LOCAL_GL_FLOAT_MAT4x2:
-                return 8;
+            case LOCAL_GL_FLOAT_MAT2x4:
+            case LOCAL_GL_FLOAT_MAT4x2:
+                return 8;
             case LOCAL_GL_FLOAT_MAT3:
                 return 9;
-            case LOCAL_GL_FLOAT_MAT3x4:
-            case LOCAL_GL_FLOAT_MAT4x3:
-                return 12;
+            case LOCAL_GL_FLOAT_MAT3x4:
+            case LOCAL_GL_FLOAT_MAT4x3:
+                return 12;
             case LOCAL_GL_FLOAT_MAT4:
                 return 16;
             default:
                 MOZ_ASSERT(false); // should never get here
                 return 0;
         }
     }
 };
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -231,18 +231,16 @@ private:
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000);
   DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520);
   DECL_GFX_PREF(Live, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.decodeondraw",                ImageMemDecodeOnDraw, bool, false);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
-  DECL_GFX_PREF(Live, "image.mem.hard_limit_decoded_image_kb", ImageMemHardLimitDecodedImageKB, uint32_t, 0);
-  DECL_GFX_PREF(Live, "image.mem.max_decoded_image_kb",        ImageMemMaxDecodedImageKB, uint32_t, 50*1024);
   DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield",         ImageMemMaxMSBeforeYield, uint32_t, 400);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mozsamplesize.enabled",           ImageMozSampleSizeEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.multithreaded_decoding.enabled",  ImageMTDecodingEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
--- a/image/build/nsImageModule.cpp
+++ b/image/build/nsImageModule.cpp
@@ -14,17 +14,16 @@
 #include "ShutdownTracker.h"
 #include "SurfaceCache.h"
 
 #include "gfxPrefs.h"
 #include "imgLoader.h"
 #include "imgRequest.h"
 #include "imgRequestProxy.h"
 #include "imgTools.h"
-#include "DiscardTracker.h"
 
 #include "nsICOEncoder.h"
 #include "nsPNGEncoder.h"
 #include "nsJPEGEncoder.h"
 #include "nsBMPEncoder.h"
 
 // objects that just require generic constructors
 using namespace mozilla::image;
@@ -87,34 +86,32 @@ static bool sInitialized = false;
 nsresult
 mozilla::image::InitModule()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Make sure the preferences are initialized
   gfxPrefs::GetSingleton();
 
   mozilla::image::ShutdownTracker::Initialize();
-  mozilla::image::DiscardTracker::Initialize();
   mozilla::image::ImageFactory::Initialize();
   mozilla::image::RasterImage::Initialize();
   mozilla::image::SurfaceCache::Initialize();
   imgLoader::GlobalInit();
   sInitialized = true;
   return NS_OK;
 }
 
 void
 mozilla::image::ShutdownModule()
 {
   if (!sInitialized) {
     return;
   }
   imgLoader::Shutdown();
   mozilla::image::SurfaceCache::Shutdown();
-  mozilla::image::DiscardTracker::Shutdown();
   sInitialized = false;
 }
 
 static const mozilla::Module kImageModule = {
   mozilla::Module::kVersion,
   kImageCIDs,
   kImageContracts,
   kImageCategories,
deleted file mode 100644
--- a/image/src/DiscardTracker.cpp
+++ /dev/null
@@ -1,328 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "nsComponentManagerUtils.h"
-#include "nsITimer.h"
-#include "RasterImage.h"
-#include "DiscardTracker.h"
-#include "mozilla/Preferences.h"
-#include "gfxPrefs.h"
-
-namespace mozilla {
-namespace image {
-
-static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms";
-
-/* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages;
-/* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer;
-/* static */ bool DiscardTracker::sInitialized = false;
-/* static */ bool DiscardTracker::sTimerOn = false;
-/* static */ Atomic<bool> DiscardTracker::sDiscardRunnablePending(false);
-/* static */ uint64_t DiscardTracker::sCurrentDecodedImageBytes = 0;
-/* static */ uint32_t DiscardTracker::sMinDiscardTimeoutMs = 10000;
-/* static */ PRLock * DiscardTracker::sAllocationLock = nullptr;
-/* static */ Mutex* DiscardTracker::sNodeListMutex = nullptr;
-/* static */ Atomic<bool> DiscardTracker::sShutdown(false);
-
-/*
- * When we notice we're using too much memory for decoded images, we enqueue a
- * DiscardRunnable, which runs this code.
- */
-NS_IMETHODIMP
-DiscardTracker::DiscardRunnable::Run()
-{
-  sDiscardRunnablePending = false;
-
-  DiscardTracker::DiscardNow();
-  return NS_OK;
-}
-
-void
-DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
-{
-  DiscardTracker::ReloadTimeout();
-}
-
-nsresult
-DiscardTracker::Reset(Node *node)
-{
-  // We shouldn't call Reset() with a null |img| pointer, on images which can't
-  // be discarded, or on animated images (which should be marked as
-  // non-discardable, anyway).
-  MutexAutoLock lock(*sNodeListMutex);
-  MOZ_ASSERT(sInitialized);
-  MOZ_ASSERT(node->img);
-  MOZ_ASSERT(node->img->CanDiscard());
-  MOZ_ASSERT(!node->img->mAnim);
-
-  // Insert the node at the front of the list and note when it was inserted.
-  bool wasInList = node->isInList();
-  if (wasInList) {
-    node->remove();
-  }
-  node->timestamp = TimeStamp::Now();
-  sDiscardableImages.insertFront(node);
-
-  // If the node wasn't already in the list of discardable images, then we may
-  // need to discard some images to stay under gfxPrefs::ImageMemMaxDecodedImageKB() limit.
-  // Call MaybeDiscardSoon to do this check.
-  if (!wasInList) {
-    MaybeDiscardSoon();
-  }
-
-  // Make sure the timer is running.
-  nsresult rv = EnableTimer();
-  NS_ENSURE_SUCCESS(rv,rv);
-
-  return NS_OK;
-}
-
-void
-DiscardTracker::Remove(Node *node)
-{
-  if (sShutdown) {
-    // Already shutdown. List should be empty, so just return.
-    return;
-  }
-  MutexAutoLock lock(*sNodeListMutex);
-
-  if (node->isInList())
-    node->remove();
-
-  if (sDiscardableImages.isEmpty())
-    DisableTimer();
-}
-
-/**
- * Shut down the tracker, deallocating the timer.
- */
-void
-DiscardTracker::Shutdown()
-{
-  sShutdown = true;
-
-  if (sTimer) {
-    sTimer->Cancel();
-    sTimer = nullptr;
-  }
-
-  // Clear the sDiscardableImages linked list so that its destructor
-  // (LinkedList.h) finds an empty array, which is required after bug 803688.
-  DiscardAll();
-
-  delete sNodeListMutex;
-  sNodeListMutex = nullptr;
-}
-
-/*
- * Discard all the images we're tracking.
- */
-void
-DiscardTracker::DiscardAll()
-{
-  MutexAutoLock lock(*sNodeListMutex);
-
-  if (!sInitialized)
-    return;
-
-  // Be careful: Calling Discard() on an image might cause it to be removed
-  // from the list!
-  Node *n;
-  while ((n = sDiscardableImages.popFirst())) {
-    n->img->Discard();
-  }
-
-  // The list is empty, so there's no need to leave the timer on.
-  DisableTimer();
-}
-
-/* static */ bool
-DiscardTracker::TryAllocation(uint64_t aBytes)
-{
-  MOZ_ASSERT(sInitialized);
-
-  PR_Lock(sAllocationLock);
-  bool enoughSpace =
-    !gfxPrefs::ImageMemHardLimitDecodedImageKB() ||
-    (gfxPrefs::ImageMemHardLimitDecodedImageKB() * 1024) - sCurrentDecodedImageBytes >= aBytes;
-
-  if (enoughSpace) {
-    sCurrentDecodedImageBytes += aBytes;
-  }
-  PR_Unlock(sAllocationLock);
-
-  // If we're using too much memory for decoded images, MaybeDiscardSoon will
-  // enqueue a callback to discard some images.
-  MaybeDiscardSoon();
-
-  return enoughSpace;
-}
-
-/* static */ void
-DiscardTracker::InformDeallocation(uint64_t aBytes)
-{
-  // This function is called back e.g. from RasterImage::Discard(); be careful!
-
-  MOZ_ASSERT(sInitialized);
-
-  PR_Lock(sAllocationLock);
-  MOZ_ASSERT(aBytes <= sCurrentDecodedImageBytes);
-  sCurrentDecodedImageBytes -= aBytes;
-  PR_Unlock(sAllocationLock);
-}
-
-/**
- * Initialize the tracker.
- */
-nsresult
-DiscardTracker::Initialize()
-{
-  // Watch the timeout pref for changes.
-  Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
-                                sDiscardTimeoutPref);
-
-  // Create the timer.
-  sTimer = do_CreateInstance("@mozilla.org/timer;1");
-
-  // Create a lock for safegarding the 64-bit sCurrentDecodedImageBytes
-  sAllocationLock = PR_NewLock();
-
-  // Create a lock for the node list.
-  MOZ_ASSERT(!sNodeListMutex);
-  sNodeListMutex = new Mutex("image::DiscardTracker");
-
-  // Mark us as initialized
-  sInitialized = true;
-
-  // Read the timeout pref and start the timer.
-  ReloadTimeout();
-
-  return NS_OK;
-}
-
-/**
- * Read the discard timeout from about:config.
- */
-void
-DiscardTracker::ReloadTimeout()
-{
-  // Read the timeout pref.
-  int32_t discardTimeout;
-  nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout);
-
-  // If we got something bogus, return.
-  if (!NS_SUCCEEDED(rv) || discardTimeout <= 0)
-    return;
-
-  // If the value didn't change, return.
-  if ((uint32_t) discardTimeout == sMinDiscardTimeoutMs)
-    return;
-
-  // Update the value.
-  sMinDiscardTimeoutMs = (uint32_t) discardTimeout;
-
-  // Restart the timer so the new timeout takes effect.
-  DisableTimer();
-  EnableTimer();
-}
-
-/**
- * Enables the timer. No-op if the timer is already running.
- */
-nsresult
-DiscardTracker::EnableTimer()
-{
-  // Nothing to do if the timer's already on or we haven't yet been
-  // initialized.  !sTimer probably means we've shut down, so just ignore that,
-  // too.
-  if (sTimerOn || !sInitialized || !sTimer)
-    return NS_OK;
-
-  sTimerOn = true;
-
-  // Activate the timer.  Have it call us back in (sMinDiscardTimeoutMs / 2)
-  // ms, so that an image is discarded between sMinDiscardTimeoutMs and
-  // (3/2 * sMinDiscardTimeoutMs) ms after it's unlocked.
-  return sTimer->InitWithFuncCallback(TimerCallback,
-                                      nullptr,
-                                      sMinDiscardTimeoutMs / 2,
-                                      nsITimer::TYPE_REPEATING_SLACK);
-}
-
-/*
- * Disables the timer. No-op if the timer isn't running.
- */
-void
-DiscardTracker::DisableTimer()
-{
-  // Nothing to do if the timer's already off.
-  if (!sTimerOn || !sTimer)
-    return;
-  sTimerOn = false;
-
-  // Deactivate
-  sTimer->Cancel();
-}
-
-/**
- * Routine activated when the timer fires. This discards everything that's
- * older than sMinDiscardTimeoutMs, and tries to discard enough images so that
- * we go under gfxPrefs::ImageMemMaxDecodedImageKB().
- */
-void
-DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
-{
-  DiscardNow();
-}
-
-void
-DiscardTracker::DiscardNow()
-{
-  // Assuming the list is ordered with oldest discard tracker nodes at the back
-  // and newest ones at the front, iterate from back to front discarding nodes
-  // until we encounter one which is new enough to keep and until we go under
-  // our gfxPrefs::ImageMemMaxDecodedImageKB() limit.
-
-  TimeStamp now = TimeStamp::Now();
-  Node* node;
-  while ((node = sDiscardableImages.getLast())) {
-    if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs ||
-        sCurrentDecodedImageBytes > gfxPrefs::ImageMemMaxDecodedImageKB() * 1024) {
-
-      // Discarding the image should cause sCurrentDecodedImageBytes to
-      // decrease via a call to InformDeallocation().
-      node->img->Discard();
-
-      // Careful: Discarding may have caused the node to have been removed
-      // from the list.
-      Remove(node);
-    }
-    else {
-      break;
-    }
-  }
-
-  // If the list is empty, disable the timer.
-  if (sDiscardableImages.isEmpty())
-    DisableTimer();
-}
-
-void
-DiscardTracker::MaybeDiscardSoon()
-{
-  // Are we carrying around too much decoded image data?  If so, enqueue an
-  // event to try to get us down under our limit.
-  if (sCurrentDecodedImageBytes > gfxPrefs::ImageMemMaxDecodedImageKB() * 1024 &&
-      !sDiscardableImages.isEmpty()) {
-    // Check if the value of sDiscardRunnablePending used to be false
-    if (!sDiscardRunnablePending.exchange(true)) {
-      nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable();
-      NS_DispatchToMainThread(runnable);
-    }
-  }
-}
-
-} // namespace image
-} // namespace mozilla
deleted file mode 100644
--- a/image/src/DiscardTracker.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#ifndef mozilla_imagelib_DiscardTracker_h_
-#define mozilla_imagelib_DiscardTracker_h_
-
-#include "mozilla/Atomics.h"
-#include "mozilla/LinkedList.h"
-#include "mozilla/Mutex.h"
-#include "mozilla/TimeStamp.h"
-#include "prlock.h"
-#include "nsThreadUtils.h"
-#include "nsAutoPtr.h"
-
-class nsITimer;
-
-namespace mozilla {
-namespace image {
-
-class RasterImage;
-
-/**
- * This static class maintains a linked list of RasterImage objects which are
- * eligible for discarding.
- *
- * When Reset() is called, the node is removed from its position in the list
- * (if it was there before) and appended to the beginnings of the list.
- *
- * Periodically (on a timer and when we notice that we're using more memory
- * than we'd like for decoded images), we go through the list and discard
- * decoded data from images at the end of the list.
- */
-class DiscardTracker
-{
-  public:
-    /**
-     * The DiscardTracker keeps a linked list of Node objects.  Each object
-     * points to a RasterImage and contains a timestamp indicating when the
-     * node was inserted into the tracker.
-     *
-     * This structure is embedded within each RasterImage object, and we do
-     * |mDiscardTrackerNode.img = this| on RasterImage construction.  Thus, a
-     * RasterImage must always call DiscardTracker::Remove() in its destructor
-     * to avoid having the tracker point to bogus memory.
-     */
-    struct Node : public LinkedListElement<Node>
-    {
-      RasterImage *img;
-      TimeStamp timestamp;
-    };
-
-    /**
-     * Add an image to the front of the tracker's list, or move it to the front
-     * if it's already in the list.  This function is main thread only.
-     */
-    static nsresult Reset(struct Node* node);
-
-    /**
-     * Remove a node from the tracker; do nothing if the node is currently
-     * untracked.  This function is main thread only.
-     */
-    static void Remove(struct Node* node);
-
-    /**
-     * Initializes the discard tracker.  This function is main thread only.
-     */
-    static nsresult Initialize();
-
-    /**
-     * Shut the discard tracker down.  This should be called on XPCOM shutdown
-     * so we destroy the discard timer's nsITimer.  This function is main thread
-     * only.
-     */
-    static void Shutdown();
-
-    /**
-     * Discard the decoded image data for all images tracked by the discard
-     * tracker.  This function is main thread only.
-     */
-    static void DiscardAll();
-
-    /**
-     * Inform the discard tracker that we are going to allocate some memory
-     * for a decoded image. We use this to determine when we've allocated
-     * too much memory and should discard some images.  This function can be
-     * called from any thread and is thread-safe. If this function succeeds, the
-     * caller is now responsible for ensuring that InformDeallocation is called.
-     */
-    static bool TryAllocation(uint64_t aBytes);
-
-    /**
-     * Inform the discard tracker that we've deallocated some memory for a
-     * decoded image. This function can be called from any thread and is
-     * thread-safe.
-     */
-    static void InformDeallocation(uint64_t aBytes);
-
-  private:
-    /**
-     * This is called when the discard timer fires; it calls into DiscardNow().
-     */
-    friend void DiscardTimeoutChangedCallback(const char* aPref, void *aClosure);
-
-    /**
-     * When run, this runnable sets sDiscardRunnablePending to false and calls
-     * DiscardNow().
-     */
-    class DiscardRunnable : public nsRunnable
-    {
-      NS_IMETHOD Run();
-    };
-
-    static void ReloadTimeout();
-    static nsresult EnableTimer();
-    static void DisableTimer();
-    static void MaybeDiscardSoon();
-    static void TimerCallback(nsITimer *aTimer, void *aClosure);
-    static void DiscardNow();
-
-    static LinkedList<Node> sDiscardableImages;
-    static nsCOMPtr<nsITimer> sTimer;
-    static bool sInitialized;
-    static bool sTimerOn;
-    static Atomic<bool> sDiscardRunnablePending;
-    static uint64_t sCurrentDecodedImageBytes;
-    static uint32_t sMinDiscardTimeoutMs;
-    static uint32_t sMaxDecodedImageKB;
-    static uint32_t sHardLimitDecodedImageKB;
-    // Lock for safegarding the 64-bit sCurrentDecodedImageBytes
-    static PRLock *sAllocationLock;
-    static Mutex* sNodeListMutex;
-    static Atomic<bool> sShutdown;
-};
-
-} // namespace image
-} // namespace mozilla
-
-#endif /* mozilla_imagelib_DiscardTracker_h_ */
--- a/image/src/DynamicImage.cpp
+++ b/image/src/DynamicImage.cpp
@@ -94,16 +94,20 @@ DynamicImage::OnImageDataComplete(nsIReq
 
 nsresult
 DynamicImage::OnNewSourceData()
 {
   return NS_OK;
 }
 
 void
+DynamicImage::OnSurfaceDiscarded()
+{ }
+
+void
 DynamicImage::SetInnerWindowID(uint64_t aInnerWindowId)
 { }
 
 uint64_t
 DynamicImage::InnerWindowID() const
 {
   return 0;
 }
--- a/image/src/DynamicImage.h
+++ b/image/src/DynamicImage.h
@@ -53,16 +53,18 @@ public:
                                         uint64_t aSourceOffset,
                                         uint32_t aCount) MOZ_OVERRIDE;
   virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
                                        nsISupports* aContext,
                                        nsresult aStatus,
                                        bool aLastPart) MOZ_OVERRIDE;
   virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
 
+  virtual void OnSurfaceDiscarded() MOZ_OVERRIDE;
+
   virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE;
   virtual uint64_t InnerWindowID() const MOZ_OVERRIDE;
 
   virtual bool HasError() MOZ_OVERRIDE;
   virtual void SetHasError() MOZ_OVERRIDE;
 
   virtual ImageURL* GetURI() MOZ_OVERRIDE;
 
--- a/image/src/Image.h
+++ b/image/src/Image.h
@@ -122,16 +122,22 @@ public:
                                        bool aLastPart) = 0;
 
   /**
    * Called for multipart images to allow for any necessary reinitialization
    * when there's a new part to add.
    */
   virtual nsresult OnNewSourceData() = 0;
 
+  /**
+   * Called when the SurfaceCache discards a persistent surface belonging to
+   * this image.
+   */
+  virtual void OnSurfaceDiscarded() = 0;
+
   virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
   virtual uint64_t InnerWindowID() const = 0;
 
   virtual bool HasError() = 0;
   virtual void SetHasError() = 0;
 
   virtual ImageURL* GetURI() = 0;
 };
@@ -151,16 +157,18 @@ public:
   }
 
   virtual void IncrementAnimationConsumers() MOZ_OVERRIDE;
   virtual void DecrementAnimationConsumers() MOZ_OVERRIDE;
 #ifdef DEBUG
   virtual uint32_t GetAnimationConsumers() MOZ_OVERRIDE { return mAnimationConsumers; }
 #endif
 
+  virtual void OnSurfaceDiscarded() MOZ_OVERRIDE { }
+
   virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE {
     mInnerWindowId = aInnerWindowId;
   }
   virtual uint64_t InnerWindowID() const MOZ_OVERRIDE { return mInnerWindowId; }
 
   virtual bool HasError() MOZ_OVERRIDE    { return mError; }
   virtual void SetHasError() MOZ_OVERRIDE { mError = true; }
 
--- a/image/src/ImageWrapper.cpp
+++ b/image/src/ImageWrapper.cpp
@@ -98,16 +98,22 @@ ImageWrapper::OnImageDataComplete(nsIReq
 
 nsresult
 ImageWrapper::OnNewSourceData()
 {
   return mInnerImage->OnNewSourceData();
 }
 
 void
+ImageWrapper::OnSurfaceDiscarded()
+{
+  return mInnerImage->OnSurfaceDiscarded();
+}
+
+void
 ImageWrapper::SetInnerWindowID(uint64_t aInnerWindowId)
 {
   mInnerImage->SetInnerWindowID(aInnerWindowId);
 }
 
 uint64_t
 ImageWrapper::InnerWindowID() const
 {
--- a/image/src/ImageWrapper.h
+++ b/image/src/ImageWrapper.h
@@ -42,16 +42,18 @@ public:
                                         uint64_t aSourceOffset,
                                         uint32_t aCount) MOZ_OVERRIDE;
   virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
                                        nsISupports* aContext,
                                        nsresult aStatus,
                                        bool aLastPart) MOZ_OVERRIDE;
   virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
 
+  virtual void OnSurfaceDiscarded() MOZ_OVERRIDE;
+
   virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE;
   virtual uint64_t InnerWindowID() const MOZ_OVERRIDE;
 
   virtual bool HasError() MOZ_OVERRIDE;
   virtual void SetHasError() MOZ_OVERRIDE;
 
   virtual ImageURL* GetURI() MOZ_OVERRIDE;
 
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -131,32 +131,16 @@ static int32_t sMaxDecodeCount = 0;
 
 
 
 static int num_containers;
 static int num_discardable_containers;
 static int64_t total_source_bytes;
 static int64_t discardable_source_bytes;
 
-/* Are we globally disabling image discarding? */
-static bool
-DiscardingEnabled()
-{
-  static bool inited;
-  static bool enabled;
-
-  if (!inited) {
-    inited = true;
-
-    enabled = (PR_GetEnv("MOZ_DISABLE_IMAGE_DISCARD") == nullptr);
-  }
-
-  return enabled;
-}
-
 class ScaleRunner : public nsRunnable
 {
   enum ScaleState
   {
     eNew,
     eReady,
     eFinish,
     eFinishWithError
@@ -323,18 +307,16 @@ RasterImage::RasterImage(ProgressTracker
   mHasBeenDecoded(false),
   mPendingAnimation(false),
   mAnimationFinished(false),
   mWantFullDecode(false),
   mPendingError(false)
 {
   mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
 
-  // Set up the discard tracker node.
-  mDiscardTrackerNode.img = this;
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 
   // Statistics
   num_containers++;
 }
 
 //******************************************************************************
 RasterImage::~RasterImage()
@@ -366,20 +348,16 @@ RasterImage::~RasterImage()
   // Release all frames from the surface cache.
   SurfaceCache::RemoveImage(ImageKey(this));
 
   mAnim = nullptr;
 
   // Total statistics
   num_containers--;
   total_source_bytes -= mSourceData.Length();
-
-  if (NS_IsMainThread()) {
-    DiscardTracker::Remove(&mDiscardTrackerNode);
-  }
 }
 
 /* static */ void
 RasterImage::Initialize()
 {
   // Create our singletons now, so we don't have to worry about what thread
   // they're created on.
   DecodePool::Singleton();
@@ -595,27 +573,36 @@ RasterImage::LookupFrame(uint32_t aFrame
     // In the multipart case we prefer to use mMultipartDecodedFrame, which is
     // the most recent one we completely decoded, rather than display the real
     // current frame and risk severe tearing.
     return mMultipartDecodedFrame->DrawableRef();
   }
 
   DrawableFrameRef ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
 
+  if (!ref && IsOpaque()) {
+    // We can use non-premultiplied alpha frames when premultipled alpha is
+    // requested, or vice versa, if this image is opaque. Try again with the bit
+    // toggled.
+    ref = LookupFrameInternal(aFrameNum, aSize,
+                              aFlags ^ FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
+  }
+
   if (!ref) {
-    // The OS threw this frame away. We need to discard and redecode.
+    // The OS threw this frame away. We need to redecode if we can.
     MOZ_ASSERT(!mAnim, "Animated frames should be locked");
-    if (CanForciblyDiscardAndRedecode()) {
-      Discard(/* aForce = */ true, /* aNotify = */ false);
-      ApplyDecodeFlags(aFlags);
-      WantDecodedFrames(aFlags, aShouldSyncNotify);
-
-      // See if we managed to redecode enough to get the frame we want.
-      ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
-    }
+
+    // Update our state so the decoder knows what to do.
+    mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK;
+    mDecoded = false;
+    mHasFirstFrame = false;
+    WantDecodedFrames(aFlags, aShouldSyncNotify);
+
+    // See if we managed to redecode enough to get the frame we want.
+    ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
 
     if (!ref) {
       // We didn't successfully redecode, so just fail.
       return DrawableFrameRef();
     }
   }
 
   // We will return a paletted frame if it's not marked as compositing failed
@@ -701,16 +688,24 @@ RasterImage::FrameRect(uint32_t aWhichFr
   // If the frame doesn't exist, we return the empty rectangle. It's not clear
   // whether this is appropriate in general, but at the moment the only
   // consumer of this method is ProgressTracker (when it wants to figure out
   // dirty rectangles to send out batched observer updates). This should
   // probably be revisited when we fix bug 503973.
   return nsIntRect();
 }
 
+void
+RasterImage::OnSurfaceDiscarded()
+{
+  if (mProgressTracker) {
+    mProgressTracker->OnDiscard();
+  }
+}
+
 uint32_t
 RasterImage::GetNumFrames() const
 {
   if (mFrameBlender) {
     return mFrameBlender->GetNumFrames();
   }
   return mHasFirstFrame ? 1 : 0;
 }
@@ -764,19 +759,16 @@ RasterImage::CopyFrame(uint32_t aWhichFr
                        bool aShouldSyncNotify /* = true */)
 {
   if (aWhichFrame > FRAME_MAX_VALUE)
     return nullptr;
 
   if (mError)
     return nullptr;
 
-  if (!ApplyDecodeFlags(aFlags))
-    return nullptr;
-
   // Get the frame. If it's not there, it's probably the caller's fault for
   // not waiting for the data to be loaded from the network or not passing
   // FLAG_SYNC_DECODE
   DrawableFrameRef frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
                                           mSize, aFlags, aShouldSyncNotify);
   if (!frameRef) {
     // The OS threw this frame away and we couldn't redecode it right now.
     return nullptr;
@@ -841,19 +833,16 @@ RasterImage::GetFrameInternal(uint32_t a
   MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
 
   if (aWhichFrame > FRAME_MAX_VALUE)
     return nullptr;
 
   if (mError)
     return nullptr;
 
-  if (!ApplyDecodeFlags(aFlags))
-    return nullptr;
-
   // Get the frame. If it's not there, it's probably the caller's fault for
   // not waiting for the data to be loaded from the network or not passing
   // FLAG_SYNC_DECODE
   DrawableFrameRef frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
                                           mSize, aFlags, aShouldSyncNotify);
   if (!frameRef) {
     // The OS threw this frame away and we couldn't redecode it.
     return nullptr;
@@ -935,17 +924,17 @@ RasterImage::GetImageContainer(LayerMana
     return NS_ERROR_NOT_AVAILABLE;
   }
   mImageContainer->SetCurrentImageInTransaction(image);
 
   *_retval = mImageContainer;
   NS_ADDREF(*_retval);
   // We only need to be careful about holding on to the image when it is
   // discardable by the OS.
-  if (CanForciblyDiscardAndRedecode()) {
+  if (CanDiscard()) {
     mImageContainerCache = mImageContainer;
     mImageContainer = nullptr;
   }
 
   return NS_OK;
 }
 
 void
@@ -1094,48 +1083,16 @@ RasterImage::InternalAddFrame(uint32_t a
   mAnim->UnionFirstFrameRefreshArea(frame->GetRect());
 
   MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender by now");
   mFrameBlender->InsertFrame(aFrameNum, frame->RawAccessRef());
 
   return ref;
 }
 
-bool
-RasterImage::ApplyDecodeFlags(uint32_t aNewFlags)
-{
-  if (mFrameDecodeFlags == (aNewFlags & DECODE_FLAGS_MASK))
-    return true; // Not asking very much of us here.
-
-  if (mDecoded) {
-    // If the requested frame is opaque and the current and new decode flags
-    // only differ in the premultiply alpha bit then we can use the existing
-    // frame, we don't need to discard and re-decode.
-    uint32_t currentNonAlphaFlags =
-      (mFrameDecodeFlags & DECODE_FLAGS_MASK) & ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
-    uint32_t newNonAlphaFlags =
-      (aNewFlags & DECODE_FLAGS_MASK) & ~FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
-    if (currentNonAlphaFlags == newNonAlphaFlags && IsOpaque()) {
-      return true;
-    }
-
-    // if we can't discard, then we're screwed; we have no way
-    // to re-decode.  Similarly if we aren't allowed to do a sync
-    // decode.
-    if (!(aNewFlags & FLAG_SYNC_DECODE))
-      return false;
-    if (!CanForciblyDiscardAndRedecode())
-      return false;
-    ForceDiscard();
-  }
-
-  mFrameDecodeFlags = aNewFlags & DECODE_FLAGS_MASK;
-  return true;
-}
-
 nsresult
 RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDecodingMonitor.AssertCurrentThreadIn();
 
   if (mError)
     return NS_ERROR_FAILURE;
@@ -1232,23 +1189,16 @@ RasterImage::DecodingComplete(imgFrame* 
   }
 
   // Flag that we're done decoding.
   // XXX - these should probably be combined when we fix animated image
   // discarding with bug 500402.
   mDecoded = true;
   mHasBeenDecoded = true;
 
-  // We now have one of the qualifications for discarding. Re-evaluate.
-  if (CanDiscard()) {
-    NS_ABORT_IF_FALSE(!DiscardingActive(),
-                      "We shouldn't have been discardable before this");
-    DiscardTracker::Reset(&mDiscardTrackerNode);
-  }
-
   bool singleFrame = GetNumFrames() == 1;
 
   // If there's only 1 frame, mark it as optimizable. Optimizing animated images
   // is not supported.
   //
   // We don't optimize the frame for multipart images because we reuse
   // the frame.
   if (singleFrame && !mMultipart && aFinalFrame) {
@@ -1567,21 +1517,16 @@ RasterImage::DoImageDataComplete()
              "is done for container %p (%s) - header %p is 0x%s (length %d)",
              this,
              mSourceDataMimeType.get(),
              mSourceData.Elements(),
              buf,
              mSourceData.Length()));
   }
 
-  // We now have one of the qualifications for discarding. Re-evaluate.
-  if (CanDiscard()) {
-    nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode);
-    CONTAINER_ENSURE_SUCCESS(rv);
-  }
   return NS_OK;
 }
 
 nsresult
 RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bool aLastPart)
 {
   nsresult finalStatus = DoImageDataComplete();
 
@@ -1732,22 +1677,21 @@ RasterImage::GetKeys(uint32_t *count, ch
     *count = 0;
     *keys = nullptr;
     return NS_OK;
   }
   return mProperties->GetKeys(count, keys);
 }
 
 void
-RasterImage::Discard(bool aForce, bool aNotify)
+RasterImage::Discard()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // We should be ok for discard
-  NS_ABORT_IF_FALSE(aForce ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!");
+  MOZ_ASSERT(CanDiscard(), "Asked to discard but can't");
 
   // We should never discard when we have an active decoder
   NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
 
   // As soon as an image becomes animated, it becomes non-discardable and any
   // timers are cancelled.
   NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
 
@@ -1761,71 +1705,45 @@ RasterImage::Discard(bool aForce, bool a
   // Clear the last decoded multipart frame.
   mMultipartDecodedFrame.reset();
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
   mHasFirstFrame = false;
 
   // Notify that we discarded
-  if (aNotify && mProgressTracker) {
+  if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 
   mDecodeStatus = DecodeStatus::INACTIVE;
 
-  if (aForce) {
-    DiscardTracker::Remove(&mDiscardTrackerNode);
-  }
-
   // Log
   PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from RasterImage %p (%s) - %d frames (cached count: %d); "
           "Total Containers: %d, Discardable containers: %d, "
           "Total source bytes: %lld, Source bytes for discardable containers %lld",
           this,
           mSourceDataMimeType.get(),
           old_frame_count,
           GetNumFrames(),
           num_containers,
           num_discardable_containers,
           total_source_bytes,
           discardable_source_bytes));
 }
 
-// Helper method to determine if we can discard an image
 bool
 RasterImage::CanDiscard() {
-  return (DiscardingEnabled() && // Globally enabled...
-          mDiscardable &&        // ...Enabled at creation time...
-          (mLockCount == 0) &&   // ...not temporarily disabled...
-          mHasSourceData &&      // ...have the source data...
-          mDecoded);             // ...and have something to discard.
-}
-
-bool
-RasterImage::CanForciblyDiscard() {
-  return mHasSourceData;         // ...have the source data...
-}
-
-bool
-RasterImage::CanForciblyDiscardAndRedecode() {
   return mHasSourceData &&       // ...have the source data...
          !mDecoder &&            // Can't discard with an open decoder
          !mAnim;                 // Can never discard animated images
 }
 
-// Helper method to tell us whether the clock is currently running for
-// discarding this image. Mainly for assertions.
-bool
-RasterImage::DiscardingActive() {
-  return mDiscardTrackerNode.isInList();
-}
-
 // Helper method to determine if we're storing the source data in a buffer
 // or just writing it directly to the decoder
 bool
 RasterImage::StoringSourceData() const {
   return !mMultipart;
 }
 
 
@@ -1835,19 +1753,16 @@ nsresult
 RasterImage::InitDecoder(bool aDoSizeDecode)
 {
   // Ensure that the decoder is not already initialized
   NS_ABORT_IF_FALSE(!mDecoder, "Calling InitDecoder() while already decoding!");
 
   // We shouldn't be firing up a decoder if we already have the frames decoded
   NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!");
 
-  // Since we're not decoded, we should not have a discard timer active
-  NS_ABORT_IF_FALSE(!DiscardingActive(), "Discard Timer active in InitDecoder()!");
-
   // Make sure we actually get size before doing a full decode.
   if (!aDoSizeDecode) {
     NS_ABORT_IF_FALSE(mHasSize, "Must do a size decode before a full decode!");
   }
 
   // Figure out which decoder we want
   eDecoderType type = GetDecoderType(mSourceDataMimeType.get());
   CONTAINER_ENSURE_TRUE(type != eDecoderType_unknown, NS_IMAGELIB_ERROR_NO_DECODER);
@@ -1990,26 +1905,16 @@ RasterImage::WriteToDecoder(const char *
 // This function is called in situations where it's clear that we want the
 // frames in decoded form (Draw, LookupFrame, etc).  If we're completely decoded,
 // this method resets the discard timer (if we're discardable), since wanting
 // the frames now is a good indicator of wanting them again soon. If we're not
 // decoded, this method kicks off asynchronous decoding to generate the frames.
 nsresult
 RasterImage::WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify)
 {
-  nsresult rv;
-
-  // If we can discard, the clock should be running. Reset it.
-  if (CanDiscard()) {
-    NS_ABORT_IF_FALSE(DiscardingActive(),
-                      "Decoded and discardable but discarding not activated!");
-    rv = DiscardTracker::Reset(&mDiscardTrackerNode);
-    CONTAINER_ENSURE_SUCCESS(rv);
-  }
-
   // Request a decode, which does nothing if we're already decoded.
   if (aShouldSyncNotify) {
     // We can sync notify, which means we can also sync decode.
     if (aFlags & FLAG_SYNC_DECODE) {
       return SyncDecode();
     }
     return StartDecoding();
   }
@@ -2221,23 +2126,19 @@ RasterImage::SyncDecode()
   }
 
   // If we have a decoder open with different flags than what we need, shut it
   // down
   if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) {
     nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
     CONTAINER_ENSURE_SUCCESS(rv);
 
-    if (mDecoded) {
-      // If we've finished decoding we need to discard so we can re-decode
-      // with the new flags. If we can't discard then there isn't
-      // anything we can do.
-      if (!CanForciblyDiscardAndRedecode())
-        return NS_ERROR_NOT_AVAILABLE;
-      ForceDiscard();
+    if (mDecoded && mAnim) {
+      // We can't redecode animated images, so we'll have to give up.
+      return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
   // If we're currently waiting on a new frame for this image, create it now.
   if (mDecoder && mDecoder->NeedsNewFrame()) {
     mDecoder->AllocateFrame();
   }
 
@@ -2438,43 +2339,16 @@ RasterImage::Draw(gfxContext* aContext,
   // Illegal -- you can't draw with non-default decode flags.
   // (Disabling colorspace conversion might make sense to allow, but
   // we don't currently.)
   if ((aFlags & DECODE_FLAGS_MASK) != DECODE_FLAGS_DEFAULT)
     return NS_ERROR_FAILURE;
 
   NS_ENSURE_ARG_POINTER(aContext);
 
-  // We can only draw without discarding and redecoding in these cases:
-  //  * We have the default decode flags.
-  //  * We have exactly FLAG_DECODE_NO_PREMULTIPLY_ALPHA and the current frame
-  //    is opaque.
-  bool haveDefaultFlags = (mFrameDecodeFlags == DECODE_FLAGS_DEFAULT);
-  bool haveSafeAlphaFlags =
-    (mFrameDecodeFlags == FLAG_DECODE_NO_PREMULTIPLY_ALPHA) && IsOpaque();
-
-  if (!(haveDefaultFlags || haveSafeAlphaFlags)) {
-    if (!CanForciblyDiscardAndRedecode())
-      return NS_ERROR_NOT_AVAILABLE;
-    ForceDiscard();
-
-    mFrameDecodeFlags = DECODE_FLAGS_DEFAULT;
-  }
-
-  // If this image is a candidate for discarding, reset its position in the
-  // discard tracker so we're less likely to discard it right away.
-  //
-  // (We don't normally draw unlocked images, so this conditition will usually
-  // be false.  But we will draw unlocked images if image locking is globally
-  // disabled via the image.mem.allow_locking_in_content_processes pref.)
-  if (DiscardingActive()) {
-    DiscardTracker::Reset(&mDiscardTrackerNode);
-  }
-
-
   if (IsUnlocked() && mProgressTracker) {
     mProgressTracker->OnUnlockedDraw();
   }
 
   // We use !mDecoded && mHasSourceData to mean discarded.
   if (!mDecoded && mHasSourceData) {
     mDrawStartTime = TimeStamp::Now();
   }
@@ -2515,19 +2389,16 @@ RasterImage::Draw(gfxContext* aContext,
 NS_IMETHODIMP
 RasterImage::LockImage()
 {
   MOZ_ASSERT(NS_IsMainThread(),
              "Main thread to encourage serialization with UnlockImage");
   if (mError)
     return NS_ERROR_FAILURE;
 
-  // Cancel the discard timer if it's there
-  DiscardTracker::Remove(&mDiscardTrackerNode);
-
   // Increment the lock count
   mLockCount++;
 
   // Lock this image's surfaces in the SurfaceCache.
   if (mLockCount == 1) {
     SurfaceCache::LockImage(ImageKey(this));
   }
 
@@ -2545,59 +2416,50 @@ RasterImage::UnlockImage()
     return NS_ERROR_FAILURE;
 
   // It's an error to call this function if the lock count is 0
   NS_ABORT_IF_FALSE(mLockCount > 0,
                     "Calling UnlockImage with mLockCount == 0!");
   if (mLockCount == 0)
     return NS_ERROR_ABORT;
 
-  // We're locked, so discarding should not be active
-  NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated");
-
   // Decrement our lock count
   mLockCount--;
 
   // Unlock this image's surfaces in the SurfaceCache.
   if (mLockCount == 0) {
     SurfaceCache::UnlockImage(ImageKey(this));
   }
 
   // If we've decoded this image once before, we're currently decoding again,
   // and our lock count is now zero (so nothing is forcing us to keep the
   // decoded data around), try to cancel the decode and throw away whatever
   // we've decoded.
-  if (mHasBeenDecoded && mDecoder &&
-      mLockCount == 0 && CanForciblyDiscard()) {
+  if (mHasBeenDecoded && mDecoder && mLockCount == 0 && !mAnim) {
     PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
            ("RasterImage[0x%p] canceling decode because image "
             "is now unlocked.", this));
     ReentrantMonitorAutoEnter lock(mDecodingMonitor);
     FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
-    ForceDiscard();
     return NS_OK;
   }
 
-  // Otherwise, we might still be a candidate for discarding in the future.  If
-  // we are, add ourselves to the discard tracker.
-  if (CanDiscard()) {
-    nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode);
-    CONTAINER_ENSURE_SUCCESS(rv);
-  }
-
   return NS_OK;
 }
 
 //******************************************************************************
 /* void requestDiscard() */
 NS_IMETHODIMP
 RasterImage::RequestDiscard()
 {
-  if (CanDiscard() && CanForciblyDiscardAndRedecode()) {
-    ForceDiscard();
+  if (mDiscardable &&      // Enabled at creation time...
+      mLockCount == 0 &&   // ...not temporarily disabled...
+      mDecoded &&          // ...and have something to discard.
+      CanDiscard()) {
+    Discard();
   }
 
   return NS_OK;
 }
 
 // Flushes up to aMaxBytes to the decoder.
 nsresult
 RasterImage::DecodeSomeData(size_t aMaxBytes, DecodeStrategy aStrategy)
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -21,17 +21,16 @@
 #include "FrameBlender.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DecodePool.h"
-#include "DiscardTracker.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypedEnum.h"
 #include "mozilla/WeakPtr.h"
@@ -156,33 +155,33 @@ public:
 
   virtual nsresult StartAnimation();
   virtual nsresult StopAnimation();
 
   // Methods inherited from Image
   nsresult Init(const char* aMimeType,
                 uint32_t aFlags);
   virtual nsIntRect FrameRect(uint32_t aWhichFrame) MOZ_OVERRIDE;
+  virtual void OnSurfaceDiscarded() MOZ_OVERRIDE;
 
   // Raster-specific methods
   static NS_METHOD WriteToRasterImage(nsIInputStream* aIn, void* aClosure,
                                       const char* aFromRawSegment,
                                       uint32_t aToOffset, uint32_t aCount,
                                       uint32_t* aWriteCount);
 
   /* The total number of frames in this image. */
   uint32_t GetNumFrames() const;
 
   virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const;
   virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
                                MallocSizeOf aMallocSizeOf) const;
 
   /* Triggers discarding. */
-  void Discard(bool aForce = false, bool aNotify = true);
-  void ForceDiscard() { Discard(/* aForce = */ true); }
+  void Discard();
 
   /* Callbacks for decoders */
   /** Sets the size and inherent orientation of the container. This should only
    * be called by the decoder. This function may be called multiple times, but
    * will throw an error if subsequent calls do not match the first.
    */
   nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation);
 
@@ -314,18 +313,16 @@ private:
   RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
                                      const nsIntRect& aFrameRect,
                                      uint32_t aDecodeFlags,
                                      gfx::SurfaceFormat aFormat,
                                      uint8_t aPaletteDepth,
                                      imgFrame* aPreviousFrame);
   nsresult DoImageDataComplete();
 
-  bool ApplyDecodeFlags(uint32_t aNewFlags);
-
   already_AddRefed<layers::Image> GetCurrentImage();
   void UpdateImageContainer();
 
   enum RequestDecodeType {
       ASYNCHRONOUS,
       SYNCHRONOUS_NOTIFY,
       SYNCHRONOUS_NOTIFY_AND_SOME_DECODE
   };
@@ -359,25 +356,22 @@ private: // data
 
   nsCOMPtr<nsIProperties>   mProperties;
 
   // IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure
   // that the frames actually exist (they may have been discarded to save memory, or
   // we maybe decoding on draw).
   UniquePtr<FrameAnimator> mAnim;
 
-  // Discard members
+  // Image locking.
   uint32_t                   mLockCount;
-  DiscardTracker::Node       mDiscardTrackerNode;
 
   // Source data members
   nsCString                  mSourceDataMimeType;
 
-  friend class DiscardTracker;
-
   // How many times we've decoded this image.
   // This is currently only used for statistics
   int32_t                        mDecodeCount;
 
   // If the image contains multiple resolutions, a hint as to which one should be used
   nsIntSize                  mRequestedResolution;
 
   // A hint for image decoder that directly scale the image to smaller buffer
@@ -492,19 +486,16 @@ private: // data
   private:
     explicit HandleErrorWorker(RasterImage* aImage);
 
     nsRefPtr<RasterImage> mImage;
   };
 
   // Helpers
   bool CanDiscard();
-  bool CanForciblyDiscard();
-  bool CanForciblyDiscardAndRedecode();
-  bool DiscardingActive();
   bool StoringSourceData() const;
 
 protected:
   explicit RasterImage(ProgressTracker* aProgressTracker = nullptr,
                        ImageURL* aURI = nullptr);
 
   bool ShouldAnimate();
 
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
 #include "nsIMemoryReporter.h"
 #include "gfx2DGlue.h"
 #include "gfxPattern.h"  // Workaround for flaw in bug 921753 part 2.
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "imgFrame.h"
+#include "Image.h"
 #include "nsAutoPtr.h"
 #include "nsExpirationTracker.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "nsSize.h"
 #include "nsTArray.h"
 #include "prsystem.h"
 #include "SVGImageContext.h"
@@ -346,21 +347,26 @@ public:
     StartTracking(surface);
 
     return true;
   }
 
   void Remove(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
-    const ImageKey imageKey = aSurface->GetImageKey();
+    ImageKey imageKey = aSurface->GetImageKey();
 
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
     MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
 
+    // If the surface was persistent, tell its image that we discarded it.
+    if (aSurface->GetLifetime() == Lifetime::Persistent) {
+      static_cast<Image*>(imageKey)->OnSurfaceDiscarded();
+    }
+
     StopTracking(aSurface);
     cache->Remove(aSurface);
 
     // Remove the per-image cache if it's unneeded now. (Keep it if the image is
     // locked, since the per-image cache is where we store that state.)
     if (cache->IsEmpty() && !cache->IsLocked()) {
       mImageCaches.Remove(imageKey);
     }
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -1,30 +1,30 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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 "imgFrame.h"
 #include "ImageRegion.h"
-#include "DiscardTracker.h"
 #include "ShutdownTracker.h"
 
 #include "prenv.h"
 
 #include "gfx2DGlue.h"
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
 #include "gfxAlphaRecovery.h"
 
 static bool gDisableOptimize = false;
 
 #include "GeckoProfiler.h"
 #include "mozilla/Likely.h"
+#include "MainThreadUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsMargin.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/gfx/Tools.h"
 
 
 namespace mozilla {
 
@@ -135,36 +135,31 @@ imgFrame::imgFrame() :
   mTimeout(100),
   mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
   mLockCount(0),
   mBlendMethod(1), /* imgIContainer::kBlendOver */
   mSinglePixel(false),
   mCompositingFailed(false),
   mHasNoAlpha(false),
   mNonPremult(false),
-  mOptimizable(false),
-  mInformedDiscardTracker(false)
+  mOptimizable(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
   }
 }
 
 imgFrame::~imgFrame()
 {
   moz_free(mPalettedImageData);
   mPalettedImageData = nullptr;
-
-  if (mInformedDiscardTracker) {
-    DiscardTracker::InformDeallocation(4 * mSize.height * mSize.width);
-  }
 }
 
 nsresult
 imgFrame::InitForDecoder(const nsIntSize& aImageSize,
                          const nsIntRect& aRect,
                          SurfaceFormat aFormat,
                          uint8_t aPaletteDepth /* = 0 */)
 {
@@ -193,23 +188,16 @@ imgFrame::InitForDecoder(const nsIntSize
     // Use the fallible allocator here
     mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength());
     if (!mPalettedImageData)
       NS_WARNING("moz_malloc for paletted image data should succeed");
     NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
   } else {
     MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?");
 
-    // Inform the discard tracker that we are going to allocate some memory.
-    mInformedDiscardTracker =
-      DiscardTracker::TryAllocation(4 * mSize.width * mSize.height);
-    if (!mInformedDiscardTracker) {
-      NS_WARNING("Exceeded the image decode size hard limit");
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
     mVBuf = AllocateBufferForImage(mSize, mFormat);
     if (!mVBuf) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     if (mVBuf->OnHeap()) {
       int32_t stride = VolatileSurfaceStride(mSize, mFormat);
       VolatileBufferPtr<uint8_t> ptr(mVBuf);
       memset(ptr, 0, stride * mSize.height);
@@ -241,24 +229,16 @@ imgFrame::InitWithDrawable(gfxDrawable* 
 
   mImageSize = aSize.ToIntSize();
   mOffset.MoveTo(0, 0);
   mSize.SizeTo(aSize.width, aSize.height);
 
   mFormat = aFormat;
   mPaletteDepth = 0;
 
-  // Inform the discard tracker that we are going to allocate some memory.
-  mInformedDiscardTracker =
-    DiscardTracker::TryAllocation(4 * mSize.width * mSize.height);
-  if (!mInformedDiscardTracker) {
-    NS_WARNING("Exceed the image decode size hard limit");
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
   RefPtr<DrawTarget> target;
 
   bool canUseDataSurface =
     gfxPlatform::GetPlatform()->CanRenderContentToDataSurface();
 
   if (canUseDataSurface) {
     // It's safe to use data surfaces for content on this platform, so we can
     // get away with using volatile buffers.
@@ -363,23 +343,16 @@ nsresult imgFrame::Optimize()
         mSinglePixelColor.b /= mSinglePixelColor.a;
 
         // blow away the older surfaces (if they exist), to release their memory
         mVBuf = nullptr;
         mVBufPtr = nullptr;
         mImageSurface = nullptr;
         mOptSurface = nullptr;
 
-        // We just dumped most of our allocated memory, so tell the discard
-        // tracker that we're not using any at all.
-        if (mInformedDiscardTracker) {
-          DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height);
-          mInformedDiscardTracker = false;
-        }
-
         return NS_OK;
       }
     }
 
     // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment
   }
 
 #ifdef ANDROID
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -202,19 +202,16 @@ private: // data
   uint8_t      mPaletteDepth;
   int8_t       mBlendMethod;
   bool mSinglePixel;
   bool mCompositingFailed;
   bool mHasNoAlpha;
   bool mNonPremult;
   bool mOptimizable;
 
-  /** Have we called DiscardTracker::InformAllocation()? */
-  bool mInformedDiscardTracker;
-
   friend class DrawableFrameRef;
   friend class RawAccessFrameRef;
 };
 
 /**
  * A reference to an imgFrame that holds the imgFrame's surface in memory,
  * allowing drawing. If you have a DrawableFrameRef |ref| and |if (ref)| returns
  * true, then calls to Draw() and GetSurface() are guaranteed to succeed.
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -31,17 +31,16 @@
 #include "nsIDocument.h"
 #include "nsINetworkPredictor.h"
 
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 
 #include "nsIMemoryReporter.h"
 #include "Image.h"
-#include "DiscardTracker.h"
 #include "gfxPrefs.h"
 
 // we want to explore making the document own the load group
 // so we can associate the document URI with the load group.
 // until this point, we have an evil hack:
 #include "nsIHttpChannelInternal.h"
 #include "nsILoadContext.h"
 #include "nsILoadGroupChild.h"
@@ -950,37 +949,16 @@ nsresult imgLoader::CreateNewProxyForReq
   }
 
   // transfer reference to caller
   *_retval = proxyRequest;
 
   return NS_OK;
 }
 
-class imgCacheObserver MOZ_FINAL : public nsIObserver
-{
-  ~imgCacheObserver() {}
-
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-};
-
-NS_IMPL_ISUPPORTS(imgCacheObserver, nsIObserver)
-
-NS_IMETHODIMP
-imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aSomeData)
-{
-  if (strcmp(aTopic, "memory-pressure") == 0 ||
-      strcmp(aTopic, "app-theme-changed") == 0) {
-    DiscardTracker::DiscardAll();
-  }
-  return NS_OK;
-}
-
 class imgCacheExpirationTracker MOZ_FINAL
   : public nsExpirationTracker<imgCacheEntry, 3>
 {
   enum { TIMEOUT_SECONDS = 10 };
 public:
   imgCacheExpirationTracker();
 
 protected:
@@ -1011,18 +989,16 @@ void imgCacheExpirationTracker::NotifyEx
   // We can be called multiple times on the same entry. Don't do work multiple
   // times.
   if (!entry->Evicted())
     entry->Loader()->RemoveFromCache(entry);
 
   entry->Loader()->VerifyCacheSizes();
 }
 
-imgCacheObserver *gCacheObserver;
-
 double imgLoader::sCacheTimeWeight;
 uint32_t imgLoader::sCacheMaxSize;
 imgMemoryReporter* imgLoader::sMemReporter;
 
 NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference, nsIObserver)
 
 static imgLoader* gSingleton = nullptr;
 static imgLoader* gPBSingleton = nullptr;
@@ -1133,25 +1109,16 @@ imgCacheQueue & imgLoader::GetCacheQueue
 {
   bool chrome = false;
   aURI->SchemeIs("chrome", &chrome);
   return chrome ? mChromeCacheQueue : mCacheQueue;
 }
 
 void imgLoader::GlobalInit()
 {
-  gCacheObserver = new imgCacheObserver();
-  NS_ADDREF(gCacheObserver);
-
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (os) {
-    os->AddObserver(gCacheObserver, "memory-pressure", false);
-    os->AddObserver(gCacheObserver, "app-theme-changed", false);
-  }
-
   sCacheTimeWeight = gfxPrefs::ImageCacheTimeWeight() / 1000.0;
   int32_t cachesize = gfxPrefs::ImageCacheSize();
   sCacheMaxSize = cachesize > 0 ? cachesize : 0;
 
   sMemReporter = new imgMemoryReporter();
   RegisterStrongMemoryReporter(sMemReporter);
   RegisterImagesContentUsedUncompressedDistinguishedAmount(imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
 }
@@ -1279,17 +1246,16 @@ NS_IMETHODIMP imgLoader::FindEntryProper
 
   return NS_OK;
 }
 
 void imgLoader::Shutdown()
 {
   NS_IF_RELEASE(gSingleton);
   NS_IF_RELEASE(gPBSingleton);
-  NS_RELEASE(gCacheObserver);
 }
 
 nsresult imgLoader::ClearChromeImageCache()
 {
   return EvictEntries(mChromeCache);
 }
 
 nsresult imgLoader::ClearImageCache()
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -13,17 +13,16 @@ EXPORTS += [
     'Orientation.h',
     'SurfaceCache.h'
 ]
 
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
-    'DiscardTracker.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrameBlender.cpp',
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageFactory.cpp',
     'ImageMetadata.cpp',
     'ImageOps.cpp',
--- a/image/test/mochitest/test_bug399925.html
+++ b/image/test/mochitest/test_bug399925.html
@@ -14,43 +14,63 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <div id="content" style="display: none">
 <canvas id="canvas" width="100" height="100"> </canvas>  
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 399925. **/
+var triggerDiscardingManually = false;
 var pngResults = new Array();
 SimpleTest.waitForExplicitFinish();
 
 window.onload = function() {
-  // 1. Enable Discarding
-  // 2. Sets the discard timer to 500ms so we don't have to wait so long. The
-  //    actual time is nondeterministic, but is bounded by 2 * timer = 1 second.
+  // It'd be nice to reduce the discard timer here, but unfortunately we only
+  // read that pref on startup. We instead manually trigger discarding on
+  // platforms where the discard timer is too long (which we'll somewhat
+  // arbitrarily define as 'longer than 60 seconds').
+  var expirationMs =
+    SpecialPowers.getIntPref('image.mem.surfacecache.min_expiration_ms');
+  if (expirationMs > 60000) {
+    ok(true, 'Triggering discarding manually because SurfaceCache expiration ' +
+             'is ' + expirationMs + ' ms');
+    triggerDiscardingManually = true;
+  } else {
+    ok(true, 'Using normal discarding because SurfaceCache expiration ' +
+             'is ' + expirationMs + ' ms');
+  }
+
+  // Enable discarding for the test.
   SpecialPowers.pushPrefEnv({
-    'set':[['image.mem.discardable',true],
-           ['image.mem.min_discard_timeout_ms',1000]]
+    'set':[['image.mem.discardable',true]]
   }, runTest);
 }
 
 function runTest() {
-  // Create the image _after_ setting the discard timer pref
   var image = new Image();
   image.setAttribute("id", "gif");
-  image.src = "bug399925.gif";
-  document.getElementById("content").appendChild(image);
 
   // 1. Draw the canvas once on loadComplete
   // 2. Redraw the canvas and compare the results right on discard
   addCallbacks(image, drawCanvas, function() {
     drawCanvas();
     is(pngResults[0], pngResults[1], "got different rendered results");
     SimpleTest.finish();
   });
+
+  image.src = "bug399925.gif";
+  document.getElementById("content").appendChild(image);
+
+  if (triggerDiscardingManually) {
+    var request = SpecialPowers.wrap(image)
+        .QueryInterface(SpecialPowers.Ci.nsIImageLoadingContent)
+        .getRequest(SpecialPowers.Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+    setTimeout(() => request.requestDiscard(), 1000);
+  }
 }
 
 function addCallbacks(anImage, loadCompleteCallback, discardCallback) {
   var observer = new ImageDecoderObserverStub();
   observer.discard = function () {
     imgLoadingContent.removeObserver(scriptedObserver);
     discardCallback();
   }
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -569,17 +569,16 @@ pref("media.video-queue.default-size", 3
 // Enable the MediaCodec PlatformDecoderModule by default.
 pref("media.fragmented-mp4.exposed", true);
 pref("media.fragmented-mp4.enabled", true);
 pref("media.fragmented-mp4.android-media-codec.enabled", true);
 pref("media.fragmented-mp4.android-media-codec.preferred", true);
 
 // optimize images memory usage
 pref("image.mem.decodeondraw", true);
-pref("image.mem.min_discard_timeout_ms", 10000);
 
 #ifdef NIGHTLY_BUILD
 // Shumway component (SWF player) is disabled by default. Also see bug 904346.
 pref("shumway.disabled", true);
 #endif
 
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3770,39 +3770,22 @@ pref("image.mem.discardable", true);
 
 // Prevents images from automatically being decoded on load, instead allowing
 // them to be decoded on demand when they are drawn.
 pref("image.mem.decodeondraw", true);
 
 // Allows image locking of decoded image data in content processes.
 pref("image.mem.allow_locking_in_content_processes", true);
 
-// Minimum timeout for image discarding (in milliseconds). The actual time in
-// which an image must inactive for it to be discarded will vary between this
-// value and twice this value.
-//
-// This used to be 120 seconds, but having it that high causes our working
-// set to grow very large. Switching it back to 10 seconds will hopefully
-// be better.
-pref("image.mem.min_discard_timeout_ms", 10000);
-
 // Chunk size for calls to the image decoders
 pref("image.mem.decode_bytes_at_a_time", 16384);
 
 // The longest time we can spend in an iteration of an async decode
 pref("image.mem.max_ms_before_yield", 5);
 
-// The maximum amount of decoded image data we'll willingly keep around (we
-// might keep around more than this, but we'll try to get down to this value).
-pref("image.mem.max_decoded_image_kb", 51200);
-
-// Hard limit for the amount of decoded image data, 0 means we don't have the
-// hard limit for it.
-pref("image.mem.hard_limit_decoded_image_kb", 0);
-
 // Minimum timeout for expiring unused images from the surface cache, in
 // milliseconds. This controls how long we store cached temporary surfaces.
 pref("image.mem.surfacecache.min_expiration_ms", 60000); // 60ms
 
 // Maximum size for the surface cache, in kilobytes.
 pref("image.mem.surfacecache.max_size_kb", 1048576); // 1GB
 
 // The surface cache's size, within the constraints of the maximum size set
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-NSS_3_18_BETA3
+NSS_3_18_BETA4
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/security/nss/lib/nss/nss.def
+++ b/security/nss/lib/nss/nss.def
@@ -1057,14 +1057,8 @@ SECMOD_InternaltoPubMechFlags;
 ;+    global:
 CERT_AddExtensionByOID;
 CERT_GetGeneralNameTypeFromString;
 PK11_PubEncrypt;
 PK11_PrivDecrypt;
 ;+    local:
 ;+       *;
 ;+};
-;+NSS_3.18 { 	# NSS 3.18 release
-;+    global:
-PK11_SetCertificateNickname;
-;+    local:
-;+       *;
-;+};
--- a/security/nss/lib/pk11wrap/pk11cert.c
+++ b/security/nss/lib/pk11wrap/pk11cert.c
@@ -2681,19 +2681,8 @@ PK11_GetAllSlotsForCert(CERTCertificate 
 	PK11_FreeSlotList(slotList);
 	PORT_SetError(SEC_ERROR_NO_TOKEN);
 	slotList = NULL;
     }
 
     nssCryptokiObjectArray_Destroy(instances);
     return slotList;
 }
-
-SECStatus
-PK11_SetCertificateNickname(CERTCertificate *cert, const char *nickname)
-{
-    /* Can't set nickname of temp cert. */
-    if (!cert->slot || cert->pkcs11ID == CK_INVALID_HANDLE) {
-        return SEC_ERROR_INVALID_ARGS;
-    }
-    return PK11_SetObjectNickname(cert->slot, cert->pkcs11ID, nickname);
-}
-
--- a/security/nss/lib/pk11wrap/pk11pub.h
+++ b/security/nss/lib/pk11wrap/pk11pub.h
@@ -453,18 +453,16 @@ SECKEYPrivateKey * PK11_LoadPrivKey(PK11
 char * PK11_GetSymKeyNickname(PK11SymKey *symKey);
 char * PK11_GetPrivateKeyNickname(SECKEYPrivateKey *privKey);
 char * PK11_GetPublicKeyNickname(SECKEYPublicKey *pubKey);
 SECStatus PK11_SetSymKeyNickname(PK11SymKey *symKey, const char *nickname);
 SECStatus PK11_SetPrivateKeyNickname(SECKEYPrivateKey *privKey, 
 							const char *nickname);
 SECStatus PK11_SetPublicKeyNickname(SECKEYPublicKey *pubKey, 
 							const char *nickname);
-SECStatus PK11_SetCertificateNickname(CERTCertificate *cert,
-                                      const char *nickname);
 
 /* size to hold key in bytes */
 unsigned int PK11_GetKeyLength(PK11SymKey *key);
 /* size of actual secret parts of key in bits */
 /* algid is because RC4 strength is determined by the effective bits as well
  * as the key bits */
 unsigned int PK11_GetKeyStrength(PK11SymKey *key,SECAlgorithmID *algid);
 SECStatus PK11_ExtractKeyValue(PK11SymKey *symKey);
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1826,8 +1826,37 @@ Example
 ::
 
     "org.mozilla.experiments.info": {
       "_v": 2,
       "lastActive": "some.experiment.id",
       "lastActiveBranch": "control"
     }
 
+org.mozilla.uitour.treatment
+----------------------------
+
+Daily measurement reporting information about treatment tagging done
+by the UITour module.
+
+Version 1
+^^^^^^^^^
+
+Daily text values in the following properties:
+
+<tag>:
+    Array of discrete strings corresponding to calls for setTreatmentTag(tag, value).
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.uitour.treatment": {
+      "_v": 1,
+      "treatment": [
+        "optin",
+        "optin-DNT"
+      ],
+      "another-tag": [
+        "foobar-value"
+      ]
+    }
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -18,16 +18,17 @@ user_pref("dom.popup_maximum", -1);
 user_pref("dom.send_after_paint_to_content", true);
 user_pref("dom.successive_dialog_time_limit", 0);
 user_pref("signed.applets.codebase_principal_support", true);
 user_pref("browser.shell.checkDefaultBrowser", false);
 user_pref("shell.checkDefaultClient", false);
 user_pref("browser.warnOnQuit", false);
 user_pref("accessibility.typeaheadfind.autostart", false);
 user_pref("javascript.options.showInConsole", true);
+user_pref("devtools.browsertoolbox.panel", "jsdebugger");
 user_pref("devtools.errorconsole.enabled", true);
 user_pref("devtools.debugger.remote-port", 6023);
 user_pref("layout.debug.enable_data_xbl", true);
 user_pref("browser.EULA.override", true);
 user_pref("gfx.color_management.force_srgb", true);
 user_pref("network.manage-offline-status", false);
 // Disable speculative connections so they aren't reported as leaking when they're hanging around.
 user_pref("network.http.speculative-parallel-limit", 0);
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -3900,36 +3900,38 @@ nsNavHistory::RowToResult(mozIStorageVal
       // The Places root has parent == 0, but that item id does not really
       // exist. We want to set the parent only if it's a real one.
       parentId = itemParentId;
     }
   }
 
   if (IsQueryURI(url)) {
     // Special case "place:" URIs: turn them into containers.
+    if (itemId != -1) {
+      // We should never expose the history title for query nodes if the
+      // bookmark-item's title is set to null (the history title may be the
+      // query string without the place: prefix). Thus we call getItemTitle
+      // explicitly. Doing this in the SQL query would be less performant since
+      // it should be done for all results rather than only for queries.
+      nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
+      NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+
+      rv = bookmarks->GetItemTitle(itemId, title);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
     nsRefPtr<nsNavHistoryResultNode> resultNode;
     rv = QueryRowToResult(itemId, url, title, accessCount, time, favicon,
                           getter_AddRefs(resultNode));
     NS_ENSURE_SUCCESS(rv,rv);
 
     if (itemId != -1) {
       rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid,
                                resultNode->mBookmarkGuid);
       NS_ENSURE_SUCCESS(rv, rv);
-
-      // We should never expose the history title for query nodes if the
-      // bookmark-item's title is set to null (the history title may be the
-      // query string without the place: prefix). Thus we call getItemTitle
-      // explicitly. Doing this in the SQL query would be less performant since
-      // it should be done for all results rather than only for queries.
-      nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
-      NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
-
-      rv = bookmarks->GetItemTitle(itemId, resultNode->mTitle);
-      NS_ENSURE_SUCCESS(rv, rv);
     }
 
     if (itemId != -1 ||
         aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
       // RESULTS_AS_TAG_QUERY has date columns
       resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
       resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
       if (resultNode->IsFolder()) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_1105208.js
@@ -0,0 +1,24 @@
+// Test that result node for folder shortcuts get the target folder title if
+// the shortcut itself has no title set.
+add_task(function* () {
+  let shortcutInfo = yield PlacesUtils.bookmarks.insert({
+    type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: "place:folder=TOOLBAR"
+  });
+
+  let unfiledRoot =
+    PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
+  let shortcutNode = unfiledRoot.getChild(unfiledRoot.childCount - 1);
+  Assert.equal(shortcutNode.bookmarkGuid, shortcutInfo.guid);
+
+  let toolbarInfo =
+    yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.toolbarGuid);
+  Assert.equal(shortcutNode.title, toolbarInfo.title);
+
+  unfiledRoot.containerOpen = false;
+});
+
+function run_test() {
+  run_next_test();
+}
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -51,16 +51,17 @@ skip-if = os == "android"
 [test_433525_hasChildren_crash.js]
 [test_452777.js]
 [test_454977.js]
 [test_463863.js]
 [test_485442_crash_bug_nsNavHistoryQuery_GetUri.js]
 [test_486978_sort_by_date_queries.js]
 [test_536081.js]
 [test_1085291.js]
+[test_1105208.js]
 [test_adaptive.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_adaptive_bug527311.js]
 [test_analyze.js]
 [test_annotations.js]
 [test_asyncExecuteLegacyQueries.js]
 # Bug 676989: test hangs consistently on Android
--- a/toolkit/devtools/apps/Simulator.jsm
+++ b/toolkit/devtools/apps/Simulator.jsm
@@ -3,38 +3,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 Components.utils.import("resource://gre/modules/devtools/event-emitter.js");
 
 const EXPORTED_SYMBOLS = ["Simulator"];
 
-function getVersionNumber(fullVersion) {
-  return fullVersion.match(/(\d+\.\d+)/)[0];
-}
-
 const Simulator = {
   _simulators: {},
 
-  register: function (label, simulator) {
+  register: function (name, simulator) {
     // simulators register themselves as "Firefox OS X.Y"
-    let versionNumber = getVersionNumber(label);
-    this._simulators[versionNumber] = simulator;
-    this.emit("register", versionNumber);
+    this._simulators[name] = simulator;
+    this.emit("register", name);
   },
 
-  unregister: function (label) {
-    let versionNumber = getVersionNumber(label);
-    delete this._simulators[versionNumber];
-    this.emit("unregister", versionNumber);
+  unregister: function (name) {
+    delete this._simulators[name];
+    this.emit("unregister", name);
   },
 
-  availableVersions: function () {
+  availableNames: function () {
     return Object.keys(this._simulators).sort();
   },
 
-  getByVersion: function (version) {
-    return this._simulators[version];
-  }
+  getByName: function (name) {
+    return this._simulators[name];
+  },
 };
 
 EventEmitter.decorate(Simulator);