Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 09 Jun 2017 15:28:10 -0700
changeset 413740 f0c05f5e4dda7ac9d0cf597c5c3f3634a73f21e3
parent 413719 d0e38542a05b17dd87586281b5d324112d38f6ca (current diff)
parent 413739 70c026121670bd42e43bb87334fec1fd848b77d9 (diff)
child 413768 c4e74cfbf7e9d8e297e214478d25e3456f858cea
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.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 inbound to central, a=merge MozReview-Commit-ID: IrZXJHbiqpa
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -385,18 +385,18 @@ var gPluginHandler = {
 
       if (actions.length == 1) {
         let pluginInfo = actions[0];
         let pluginName = pluginInfo.pluginName;
 
         switch (pluginInfo.fallbackType) {
           case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
             message = gNavigatorBundle.getFormattedString(
-              "pluginActivateNew.message",
-              [pluginName, origin]);
+              "pluginActivationWarning.message",
+              [brand]);
             break;
           case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
             message = gNavigatorBundle.getFormattedString(
               "pluginActivateOutdated.message",
               [pluginName, origin, brand]);
             break;
           case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
             message = gNavigatorBundle.getFormattedString(
--- a/browser/base/content/browser-sidebar.js
+++ b/browser/base/content/browser-sidebar.js
@@ -15,32 +15,42 @@
  *  - sidebartitle or label (in that order) specify the title to
  *                 display on the sidebar.
  *  - checked      indicates whether the sidebar is currently displayed.
  *                 Note that toggleSidebar updates this attribute when
  *                 it changes the sidebar's visibility.
  *  - group        this attribute must be set to "sidebar".
  */
 var SidebarUI = {
-  browser: null,
+  // Avoid getting the browser element from init() to avoid triggering the
+  // <browser> constructor during startup if the sidebar is hidden.
+  get browser() {
+    if (this._browser)
+      return this._browser;
+    return this._browser = document.getElementById("sidebar");
+  },
   POSITION_START_PREF: "sidebar.position_start",
 
   _box: null,
-  _title: null,
+  // The constructor of this label accesses the browser element due to the
+  // control="sidebar" attribute, so avoid getting this label during startup.
+  get _title() {
+    if (this.__title)
+      return this.__title;
+    return this.__title = document.getElementById("sidebar-title");
+  },
   _splitter: null,
   _icon: null,
   _reversePositionButton: null,
   _switcherPanel: null,
   _switcherTarget: null,
   _switcherArrow: null,
 
   init() {
     this._box = document.getElementById("sidebar-box");
-    this.browser = document.getElementById("sidebar");
-    this._title = document.getElementById("sidebar-title");
     this._splitter = document.getElementById("sidebar-splitter");
     this._icon = document.getElementById("sidebar-icon");
     this._reversePositionButton = document.getElementById("sidebar-reverse-position");
     this._switcherPanel = document.getElementById("sidebarMenu-popup");
     this._switcherTarget = document.getElementById("sidebar-switcher-target");
     this._switcherArrow = document.getElementById("sidebar-switcher-arrow");
 
     this._switcherTarget.addEventListener("command", () => {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -812,29 +812,30 @@
                 // if onLocationChange was triggered by a pushState or a
                 // replaceState (bug 550565) or a hash change (bug 408415).
                 if (!this.mTab.hasAttribute("pending") &&
                     aWebProgress.isLoadingDocument &&
                     !isSameDocument) {
                   this.mBrowser.mIconURL = null;
                 }
 
-                let unifiedComplete = this.mTabBrowser._unifiedComplete;
                 let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
                 if (this.mBrowser.registeredOpenURI) {
-                  unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI,
-                                                     userContextId);
+                  this.mTabBrowser._unifiedComplete
+                                  .unregisterOpenPage(this.mBrowser.registeredOpenURI,
+                                                      userContextId);
                   delete this.mBrowser.registeredOpenURI;
                 }
                 // Tabs in private windows aren't registered as "Open" so
                 // that they don't appear as switch-to-tab candidates.
                 if (!isBlankPageURL(aLocation.spec) &&
                     (!PrivateBrowsingUtils.isWindowPrivate(window) ||
                     PrivateBrowsingUtils.permanentPrivateBrowsing)) {
-                  unifiedComplete.registerOpenPage(aLocation, userContextId);
+                  this.mTabBrowser._unifiedComplete
+                                  .registerOpenPage(aLocation, userContextId);
                   this.mBrowser.registeredOpenURI = aLocation;
                 }
               }
 
               if (!this.mBlank) {
                 this._callProgressListeners("onLocationChange",
                                             [aWebProgress, aRequest, aLocation,
                                              aFlags]);
--- a/browser/base/content/test/performance/browser_startup.js
+++ b/browser/base/content/test/performance/browser_startup.js
@@ -42,32 +42,33 @@ const startupPhases = {
       "resource://gre/modules/RemotePageManager.jsm", // bug 1369466
       "resource://gre/modules/Promise.jsm" // bug 1368456
     ])
   }},
 
   // For the following phases of startup we have only a black list for now
 
   // We are at this phase after creating the first browser window (ie. after final-ui-startup).
-  "before opening first browser window": {blacklist: {
-    components: new Set([
-      "nsSearchService.js",
-    ])
-  }},
+  "before opening first browser window": {},
 
   // We reach this phase right after showing the first browser window.
   // This means that anything already loaded at this point has been loaded
   // before first paint and delayed it.
-  "before first paint": {},
+  "before first paint": {blacklist: {
+    components: new Set([
+      "nsSearchService.js",
+      "UnifiedComplete.js",
+    ])
+  }},
 
   // We are at this phase once we are ready to handle user events.
   // Anything loaded at this phase or before gets in the way of the user
   // interacting with the first browser window.
   "before handling user events": {},
-}
+};
 
 function test() {
   if (!AppConstants.NIGHTLY_BUILD && !AppConstants.DEBUG) {
     ok(!("@mozilla.org/test/startuprecorder;1" in Cc),
        "the startup recorder component shouldn't exist in this non-nightly non-debug build.");
     return;
   }
 
--- a/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
+++ b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
@@ -67,13 +67,9 @@ add_task(async function() {
     plugin.remove();
   });
 });
 
 add_task(async function() {
   await promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small_2.html");
   let notification = await waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
   ok(notification, "There should be a notification shown for the new page.");
-  // Ensure that the notification is showing information about
-  // the x-second-test plugin.
-  let label = notification.label;
-  ok(label.includes("Second Test"), "Should mention the second plugin");
 });
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -116,17 +116,19 @@ file, You can obtain one at http://mozil
                                    GetStringFromName("pasteAndGo.label");
           pasteAndGo.setAttribute("label", label);
           pasteAndGo.setAttribute("anonid", "paste-and-go");
           pasteAndGo.setAttribute("oncommand",
               "gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
           cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
         }
 
-        this._enableOrDisableOneOffSearches();
+        this.popup.addEventListener("popupshowing", () => {
+          this._enableOrDisableOneOffSearches();
+        }, {capturing: true, once: true});
 
         // The autocomplete controller uses heuristic on some internal caches
         // to handle cases like backspace, autofill or repeated searches.
         // Ensure to clear those internal caches when switching tabs.
         gBrowser.tabContainer.addEventListener("TabSelect", this);
       ]]></constructor>
 
       <destructor><![CDATA[
@@ -2758,17 +2760,17 @@ file, You can obtain one at http://mozil
             };
             button2 = {
               label: "pluginActivateAlways.label",
               accesskey: "pluginActivateAlways.accesskey",
               action: "_singleActivateAlways"
             };
             switch (action.blocklistState) {
             case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
-              label = "pluginActivateNew.message";
+              label = "pluginActivate2.message";
               linkLabel = "pluginActivate.learnMore";
               button2.default = true;
               break;
 
             case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
               label = "pluginActivateOutdated.message";
               linkLabel = "pluginActivate.updateLabel";
               button1.default = true;
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -65,44 +65,55 @@
                      onclick="handleSearchCommand(event);"
                      tooltiptext="&searchEndCap.label;"/>
         </xul:hbox>
       </xul:textbox>
     </content>
 
     <implementation implements="nsIObserver">
       <constructor><![CDATA[
+        this._textbox.placeholder = this._stringBundle.getString("searchPlaceholder");
+
         if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
           return;
 
         Services.obs.addObserver(this, "browser-search-engine-modified");
 
         this._initialized = true;
 
-        Services.search.init(aStatus => {
-          // Bail out if the binding's been destroyed
-          if (!this._initialized)
-            return;
+        (window.delayedStartupPromise || Promise.resolve()).then(() => {
+          window.requestIdleCallback(() => {
+            Services.search.init(aStatus => {
+              // Bail out if the binding's been destroyed
+              if (!this._initialized)
+                return;
 
-          if (Components.isSuccessCode(aStatus)) {
-            // Refresh the display (updating icon, etc)
-            this.updateDisplay();
-            BrowserSearch.updateOpenSearchBadge();
-          } else {
-            Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
-          }
+              if (Components.isSuccessCode(aStatus)) {
+                // Refresh the display (updating icon, etc)
+                this.updateDisplay();
+                BrowserSearch.updateOpenSearchBadge();
+              } else {
+                Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
+              }
+            });
+          });
         });
 
-        // Some accessibility tests create their own <searchbar> that doesn't
-        // use the popup binding below, so null-check oneOffButtons.
-        if (this.textbox.popup.oneOffButtons) {
-          this.textbox.popup.oneOffButtons.telemetryOrigin = "searchbar";
-          this.textbox.popup.oneOffButtons.popup = this.textbox.popup;
-          this.textbox.popup.oneOffButtons.textbox = this.textbox;
-        }
+        // Wait until the popupshowing event to avoid forcing immediate
+        // attachment of the search-one-offs binding.
+        this.textbox.popup.addEventListener("popupshowing", () => {
+          let oneOffButtons = this.textbox.popup.oneOffButtons;
+          // Some accessibility tests create their own <searchbar> that doesn't
+          // use the popup binding below, so null-check oneOffButtons.
+          if (oneOffButtons) {
+            oneOffButtons.telemetryOrigin = "searchbar";
+            oneOffButtons.popup = this.textbox.popup;
+            oneOffButtons.textbox = this.textbox;
+          }
+        }, {capturing: true, once: true});
       ]]></constructor>
 
       <destructor><![CDATA[
         this.destroy();
       ]]></destructor>
 
       <method name="destroy">
         <body><![CDATA[
@@ -281,18 +292,16 @@
 
       <method name="updateDisplay">
         <body><![CDATA[
           var uri = this.currentEngine.iconURI;
           this.setIcon(this, uri ? uri.spec : "");
 
           var name = this.currentEngine.name;
           var text = this._stringBundle.getFormattedString("searchtip", [name]);
-
-          this._textbox.placeholder = this._stringBundle.getString("searchPlaceholder");
           this._textbox.label = text;
           this._textbox.tooltipText = text;
         ]]></body>
       </method>
 
       <method name="updateGoButtonVisibility">
         <body><![CDATA[
           document.getAnonymousElementByAttribute(this, "anonid",
@@ -562,118 +571,125 @@
         }
       ]]></handler>
 
     </handlers>
   </binding>
 
   <binding id="searchbar-textbox"
       extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
-    <implementation implements="nsIObserver">
+    <implementation>
       <constructor><![CDATA[
-        const kXULNS =
-          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
         if (document.getBindingParent(this).parentNode.parentNode.localName ==
             "toolbarpaletteitem")
           return;
 
-        // Initialize fields
-        this._stringBundle = document.getBindingParent(this)._stringBundle;
-        this._suggestEnabled =
-          Services.prefs.getBoolPref("browser.search.suggest.enabled");
-
         if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll"))
           this.setAttribute("clickSelectsAll", true);
 
-        // Add items to context menu and attach controller to handle them
         var textBox = document.getAnonymousElementByAttribute(this,
                                               "anonid", "textbox-input-box");
         var cxmenu = document.getAnonymousElementByAttribute(textBox,
                                           "anonid", "input-box-contextmenu");
-        var pasteAndSearch;
-        cxmenu.addEventListener("popupshowing", function() {
-          BrowserSearch.searchBar._textbox.closePopup();
-          if (!pasteAndSearch)
-            return;
-          var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
-          var enabled = controller.isCommandEnabled("cmd_paste");
-          if (enabled)
-            pasteAndSearch.removeAttribute("disabled");
-          else
-            pasteAndSearch.setAttribute("disabled", "true");
-        });
-
-        var element, label, akey;
-
-        element = document.createElementNS(kXULNS, "menuseparator");
-        cxmenu.appendChild(element);
+        cxmenu.addEventListener("popupshowing",
+                                () => { this.initContextMenu(cxmenu); },
+                                {capturing: true, once: true});
 
         this.setAttribute("aria-owns", this.popup.id);
-
-        var insertLocation = cxmenu.firstChild;
-        while (insertLocation.nextSibling &&
-               insertLocation.getAttribute("cmd") != "cmd_paste")
-          insertLocation = insertLocation.nextSibling;
-        if (insertLocation) {
-          element = document.createElementNS(kXULNS, "menuitem");
-          label = this._stringBundle.getString("cmd_pasteAndSearch");
-          element.setAttribute("label", label);
-          element.setAttribute("anonid", "paste-and-search");
-          element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
-          cxmenu.insertBefore(element, insertLocation.nextSibling);
-          pasteAndSearch = element;
-        }
-
-        element = document.createElementNS(kXULNS, "menuitem");
-        label = this._stringBundle.getString("cmd_clearHistory");
-        akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
-        element.setAttribute("label", label);
-        element.setAttribute("accesskey", akey);
-        element.setAttribute("cmd", "cmd_clearhistory");
-        cxmenu.appendChild(element);
-
-        element = document.createElementNS(kXULNS, "menuitem");
-        label = this._stringBundle.getString("cmd_showSuggestions");
-        akey = this._stringBundle.getString("cmd_showSuggestions_accesskey");
-        element.setAttribute("anonid", "toggle-suggest-item");
-        element.setAttribute("label", label);
-        element.setAttribute("accesskey", akey);
-        element.setAttribute("cmd", "cmd_togglesuggest");
-        element.setAttribute("type", "checkbox");
-        element.setAttribute("checked", this._suggestEnabled);
-        element.setAttribute("autocheck", "false");
-        this._suggestMenuItem = element;
-        cxmenu.appendChild(element);
-
-        this.addEventListener("keypress", aEvent => {
-          if (navigator.platform.startsWith("Mac") && aEvent.keyCode == KeyEvent.VK_F4)
-            this.openSearch()
-        }, true);
-
-        this.controllers.appendController(this.searchbarController);
         document.getBindingParent(this)._textboxInitialized = true;
-
-        // Add observer for suggest preference
-        Services.prefs.addObserver("browser.search.suggest.enabled", this);
       ]]></constructor>
 
       <destructor><![CDATA[
-        Services.prefs.removeObserver("browser.search.suggest.enabled", this);
-
-        // Because XBL and the customize toolbar code interacts poorly,
-        // there may not be anything to remove here
+        // If the context menu has never been opened, there won't be anything
+        // to remove here.
+        // Also, XBL and the customize toolbar code sometimes interact poorly.
         try {
           this.controllers.removeController(this.searchbarController);
         } catch (ex) { }
       ]]></destructor>
 
-      <field name="_stringBundle"/>
-      <field name="_suggestMenuItem"/>
-      <field name="_suggestEnabled"/>
+      // Add items to context menu and attach controller to handle them the
+      // first time the context menu is opened.
+      <method name="initContextMenu">
+        <parameter name="aMenu"/>
+        <body><![CDATA[
+          const kXULNS =
+            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+          let stringBundle = document.getBindingParent(this)._stringBundle;
+
+          let pasteAndSearch, suggestMenuItem;
+          let element, label, akey;
+
+          element = document.createElementNS(kXULNS, "menuseparator");
+          aMenu.appendChild(element);
+
+          let insertLocation = aMenu.firstChild;
+          while (insertLocation.nextSibling &&
+                 insertLocation.getAttribute("cmd") != "cmd_paste")
+            insertLocation = insertLocation.nextSibling;
+          if (insertLocation) {
+            element = document.createElementNS(kXULNS, "menuitem");
+            label = stringBundle.getString("cmd_pasteAndSearch");
+            element.setAttribute("label", label);
+            element.setAttribute("anonid", "paste-and-search");
+            element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
+            aMenu.insertBefore(element, insertLocation.nextSibling);
+            pasteAndSearch = element;
+          }
+
+          element = document.createElementNS(kXULNS, "menuitem");
+          label = stringBundle.getString("cmd_clearHistory");
+          akey = stringBundle.getString("cmd_clearHistory_accesskey");
+          element.setAttribute("label", label);
+          element.setAttribute("accesskey", akey);
+          element.setAttribute("cmd", "cmd_clearhistory");
+          aMenu.appendChild(element);
+
+          element = document.createElementNS(kXULNS, "menuitem");
+          label = stringBundle.getString("cmd_showSuggestions");
+          akey = stringBundle.getString("cmd_showSuggestions_accesskey");
+          element.setAttribute("anonid", "toggle-suggest-item");
+          element.setAttribute("label", label);
+          element.setAttribute("accesskey", akey);
+          element.setAttribute("cmd", "cmd_togglesuggest");
+          element.setAttribute("type", "checkbox");
+          element.setAttribute("autocheck", "false");
+          suggestMenuItem = element;
+          aMenu.appendChild(element);
+
+          if (AppConstants.platform == "macosx") {
+            this.addEventListener("keypress", aEvent => {
+              if (aEvent.keyCode == KeyEvent.DOM_VK_F4)
+                this.openSearch()
+            }, true);
+          }
+
+          this.controllers.appendController(this.searchbarController);
+
+          let onpopupshowing = function() {
+            BrowserSearch.searchBar._textbox.closePopup();
+            if (suggestMenuItem) {
+              let enabled =
+                Services.prefs.getBoolPref("browser.search.suggest.enabled");
+              suggestMenuItem.setAttribute("checked", enabled);
+            }
+
+            if (!pasteAndSearch)
+              return;
+            let controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+            let enabled = controller.isCommandEnabled("cmd_paste");
+            if (enabled)
+              pasteAndSearch.removeAttribute("disabled");
+            else
+              pasteAndSearch.setAttribute("disabled", "true");
+          };
+          aMenu.addEventListener("popupshowing", onpopupshowing);
+          onpopupshowing();
+        ]]></body>
+      </method>
 
       <!--
         This overrides the searchParam property in autocomplete.xml.  We're
         hijacking this property as a vehicle for delivering the privacy
         information about the window into the guts of nsSearchSuggestions.
 
         Note that the setter is the same as the parent.  We were not sure whether
         we can override just the getter.  If that proves to be the case, the setter
@@ -744,29 +760,16 @@
             popup.setAttribute("width", width > 100 ? width : 100);
 
             var yOffset = outerRect.bottom - innerRect.bottom;
             popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false);
           }
         ]]></body>
       </method>
 
-      <method name="observe">
-        <parameter name="aSubject"/>
-        <parameter name="aTopic"/>
-        <parameter name="aData"/>
-        <body><![CDATA[
-          if (aTopic == "nsPref:changed") {
-            this._suggestEnabled =
-              Services.prefs.getBoolPref("browser.search.suggest.enabled");
-            this._suggestMenuItem.setAttribute("checked", this._suggestEnabled);
-          }
-        ]]></body>
-      </method>
-
       <method name="openSearch">
         <body>
           <![CDATA[
             if (!this.popupOpen) {
               document.getBindingParent(this).openSuggestionsPanel();
               return false;
             }
             return true;
@@ -858,20 +861,20 @@
           switch (aCommand) {
             case "cmd_clearhistory":
               var param = this._self.getAttribute("autocompletesearchparam");
 
               BrowserSearch.searchBar.FormHistory.update({ op: "remove", fieldname: param }, null);
               this._self.value = "";
               break;
             case "cmd_togglesuggest":
-              // The pref observer will update _suggestEnabled and the menu
-              // checkmark.
+              let enabled =
+                Services.prefs.getBoolPref("browser.search.suggest.enabled");
               Services.prefs.setBoolPref("browser.search.suggest.enabled",
-                                         !this._self._suggestEnabled);
+                                         !enabled);
               break;
             default:
               // do nothing with unrecognized command
           }
         }
       })]]></field>
     </implementation>
 
--- a/browser/components/search/test/browser_426329.js
+++ b/browser/components/search/test/browser_426329.js
@@ -227,16 +227,24 @@ add_task(async function testAutocomplete
   var popup = searchBar.textbox.popup;
   let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
   searchBar.textbox.showHistoryPopup();
   await popupShownPromise;
   checkMenuEntries(searchEntries);
 });
 
 add_task(async function testClearHistory() {
+  // Open the textbox context menu to trigger controller attachment.
+  let textbox = searchBar.textbox;
+  let popupShownPromise = BrowserTestUtils.waitForEvent(textbox, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(textbox, { type: "contextmenu", button: 2 });
+  await popupShownPromise;
+  // Close the context menu.
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+
   let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
   ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
   controller.doCommand("cmd_clearhistory");
   let count = await countEntries();
   ok(count == 0, "History cleared");
 });
 
 add_task(async function asyncCleanup() {
--- a/browser/components/tests/startupRecorder.js
+++ b/browser/components/tests/startupRecorder.js
@@ -1,16 +1,22 @@
 /* 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 {classes: Cc, utils: Cu, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+let firstPaintNotification = "widget-first-paint";
+// widget-first-paint fires much later than expected on Linux.
+if (AppConstants.platform == "linux")
+  firstPaintNotification = "xul-window-visible";
 
 /**
   * The startupRecorder component observes notifications at various stages of
   * startup and records the set of JS components and modules that were already
   * loaded at each of these points.
   * The records are meant to be used by startup tests in
   * browser/base/content/test/performance
   * This component only exists in nightly and debug builds, it doesn't ship in
@@ -37,17 +43,17 @@ startupRecorder.prototype = {
 
     if (topic == "app-startup") {
       // We can't ensure our observer will be called first or last, so the list of
       // topics we observe here should avoid the topics used to trigger things
       // during startup (eg. the topics observed by nsBrowserGlue.js).
       let topics = [
         "profile-do-change", // This catches stuff loaded during app-startup
         "toplevel-window-ready", // Catches stuff from final-ui-startup
-        "widget-first-paint",
+        firstPaintNotification,
         "sessionstore-windows-restored",
       ];
       for (let t of topics)
         Services.obs.addObserver(this, t);
       return;
     }
 
     Services.obs.removeObserver(this, topic);
@@ -56,16 +62,16 @@ startupRecorder.prototype = {
       // We use idleDispatch here to record the set of loaded scripts after we
       // are fully done with startup and ready to react to user events.
       Services.tm.mainThread.idleDispatch(
         this.record.bind(this, "before handling user events"));
     } else {
       const topicsToNames = {
         "profile-do-change": "before profile selection",
         "toplevel-window-ready": "before opening first browser window",
-        "widget-first-paint": "before first paint",
       };
+      topicsToNames[firstPaintNotification] = "before first paint";
       this.record(topicsToNames[topic]);
     }
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([startupRecorder]);
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -267,21 +267,26 @@ crashedpluginsMessage.learnMore=Learn More…
 # whether he rather wanted to visit the host.  %S is the recognized host.
 keywordURIFixup.message=Did you mean to go to %S?
 keywordURIFixup.goTo=Yes, take me to %S
 keywordURIFixup.goTo.accesskey=Y
 keywordURIFixup.dismiss=No thanks
 keywordURIFixup.dismiss.accesskey=N
 
 ## Plugin doorhanger strings
-# LOCALIZATION NOTE (pluginActivateNew.message): Used for newly-installed
-# plugins which are not known to be unsafe. %1$S is the plugin name and %2$S
-# is the site domain.
-pluginActivateNew.message=Allow %2$S to run “%1$S”?
+# LOCALIZATION NOTE (pluginActivate2.message):
+# Used for normal plugin activation if we don't know of a specific security issue.
+# %1$S is the plugin name, %2$S is the domain, and %3$S is brandShortName.
+pluginActivate2.message=Would you like to allow %2$S to run %1$S? Plugins may slow %3$S.
 pluginActivateMultiple.message=Allow %S to run plugins?
+
+# LOCALIZATION NOTE (pluginActivationWarning.message): this should use the
+# same string as "pluginActivationWarning" in pluginproblem.dtd
+pluginActivationWarning.message=This site uses a plugin that may slow %S.
+
 pluginActivate.learnMore=Learn More…
 # LOCALIZATION NOTE (pluginActivateOutdated.message, pluginActivateOutdated.label):
 # These strings are used when an unsafe plugin has an update available.
 # %1$S is the plugin name, %2$S is the domain, and %3$S is brandShortName.
 pluginActivateOutdated.message=%3$S has prevented the outdated plugin “%1$S” from running on %2$S.
 pluginActivateOutdated.label=Outdated plugin
 pluginActivate.updateLabel=Update now…
 # LOCALIZATION NOTE (pluginActivateVulnerable.message, pluginActivateVulnerable.label):
--- a/devtools/client/netmonitor/src/components/request-list-column-waterfall.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-waterfall.js
@@ -24,34 +24,36 @@ const UPDATED_WATERFALL_PROPS = [
 const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
 
 const RequestListColumnWaterfall = createClass({
   displayName: "RequestListColumnWaterfall",
 
   propTypes: {
     firstRequestStartedMillis: PropTypes.number.isRequired,
     item: PropTypes.object.isRequired,
+    onWaterfallMouseDown: PropTypes.func.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item) ||
       this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis;
   },
 
   render() {
-    let { firstRequestStartedMillis, item } = this.props;
+    let { firstRequestStartedMillis, item, onWaterfallMouseDown } = this.props;
     const { boxes, tooltip } = timingBoxes(item);
 
     return (
       div({ className: "requests-list-column requests-list-waterfall", title: tooltip },
         div({
           className: "requests-list-timings",
           style: {
             paddingInlineStart: `${item.startedMillis - firstRequestStartedMillis}px`,
           },
+          onMouseDown: onWaterfallMouseDown,
         },
           boxes,
         )
       )
     );
   }
 });
 
--- a/devtools/client/netmonitor/src/components/request-list-content.js
+++ b/devtools/client/netmonitor/src/components/request-list-content.js
@@ -40,16 +40,17 @@ const RequestListContent = createClass({
     displayedRequests: PropTypes.object.isRequired,
     firstRequestStartedMillis: PropTypes.number.isRequired,
     fromCache: PropTypes.bool,
     onCauseBadgeMouseDown: PropTypes.func.isRequired,
     onItemMouseDown: PropTypes.func.isRequired,
     onSecurityIconMouseDown: PropTypes.func.isRequired,
     onSelectDelta: PropTypes.func.isRequired,
     onThumbnailMouseDown: PropTypes.func.isRequired,
+    onWaterfallMouseDown: PropTypes.func.isRequired,
     scale: PropTypes.number,
     selectedRequestId: PropTypes.string,
   },
 
   componentWillMount() {
     const { dispatch } = this.props;
     this.contextMenu = new RequestListContextMenu({
       cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
@@ -224,16 +225,17 @@ const RequestListContent = createClass({
     const {
       columns,
       displayedRequests,
       firstRequestStartedMillis,
       onCauseBadgeMouseDown,
       onItemMouseDown,
       onSecurityIconMouseDown,
       onThumbnailMouseDown,
+      onWaterfallMouseDown,
       selectedRequestId,
     } = this.props;
 
     return (
       div({ className: "requests-list-wrapper"},
         div({ className: "requests-list-table"},
           div({
             ref: "contentEl",
@@ -250,16 +252,17 @@ const RequestListContent = createClass({
               isSelected: item.id === selectedRequestId,
               key: item.id,
               onContextMenu: this.onContextMenu,
               onFocusedNodeChange: this.onFocusedNodeChange,
               onMouseDown: () => onItemMouseDown(item.id),
               onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause),
               onSecurityIconMouseDown: () => onSecurityIconMouseDown(item.securityState),
               onThumbnailMouseDown: () => onThumbnailMouseDown(),
+              onWaterfallMouseDown: () => onWaterfallMouseDown(),
             }))
           )
         )
       )
     );
   },
 });
 
@@ -294,10 +297,16 @@ module.exports = connect(
     onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)),
     /**
      * A handler that opens the response tab in the details view if
      * the thumbnail is clicked.
      */
     onThumbnailMouseDown: () => {
       dispatch(Actions.selectDetailsPanelTab("response"));
     },
+    /**
+     * A handler that opens the timing sidebar panel if the waterfall is clicked.
+     */
+    onWaterfallMouseDown: () => {
+      dispatch(Actions.selectDetailsPanelTab("timings"));
+    },
   }),
 )(RequestListContent);
--- a/devtools/client/netmonitor/src/components/request-list-item.js
+++ b/devtools/client/netmonitor/src/components/request-list-item.js
@@ -82,16 +82,17 @@ const RequestListItem = createClass({
     firstRequestStartedMillis: PropTypes.number.isRequired,
     fromCache: PropTypes.bool,
     onCauseBadgeMouseDown: PropTypes.func.isRequired,
     onContextMenu: PropTypes.func.isRequired,
     onFocusedNodeChange: PropTypes.func,
     onMouseDown: PropTypes.func.isRequired,
     onSecurityIconMouseDown: PropTypes.func.isRequired,
     onThumbnailMouseDown: PropTypes.func.isRequired,
+    onWaterfallMouseDown: PropTypes.func.isRequired,
     waterfallWidth: PropTypes.number,
   },
 
   componentDidMount() {
     if (this.props.isSelected) {
       this.refs.listItem.focus();
     }
   },
@@ -119,16 +120,17 @@ const RequestListItem = createClass({
       isSelected,
       firstRequestStartedMillis,
       fromCache,
       onContextMenu,
       onMouseDown,
       onCauseBadgeMouseDown,
       onSecurityIconMouseDown,
       onThumbnailMouseDown,
+      onWaterfallMouseDown,
     } = this.props;
 
     let classList = ["request-list-item", index % 2 ? "odd" : "even"];
     isSelected && classList.push("selected");
     fromCache && classList.push("fromCache");
 
     return (
       div({
@@ -157,15 +159,16 @@ const RequestListItem = createClass({
           RequestListColumnStartTime({ item, firstRequestStartedMillis }),
         columns.get("endTime") &&
           RequestListColumnEndTime({ item, firstRequestStartedMillis }),
         columns.get("responseTime") &&
           RequestListColumnResponseTime({ item, firstRequestStartedMillis }),
         columns.get("duration") && RequestListColumnDuration({ item }),
         columns.get("latency") && RequestListColumnLatency({ item }),
         columns.get("waterfall") &&
-          RequestListColumnWaterfall({ item, firstRequestStartedMillis }),
+          RequestListColumnWaterfall({ item, firstRequestStartedMillis,
+                                       onWaterfallMouseDown }),
       )
     );
   }
 });
 
 module.exports = RequestListItem;
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -123,16 +123,17 @@ skip-if = (os == 'linux' && debug && bit
 [browser_net_json_custom_mime.js]
 [browser_net_json_text_mime.js]
 [browser_net_jsonp.js]
 [browser_net_large-response.js]
 [browser_net_leak_on_tab_close.js]
 [browser_net_open_request_in_tab.js]
 [browser_net_pane-collapse.js]
 [browser_net_pane-toggle.js]
+[browser_net_persistent_logs.js]
 [browser_net_post-data-01.js]
 [browser_net_post-data-02.js]
 [browser_net_post-data-03.js]
 [browser_net_post-data-04.js]
 [browser_net_prefs-and-l10n.js]
 [browser_net_prefs-reload.js]
 [browser_net_raw_headers.js]
 [browser_net_reload-button.js]
@@ -162,9 +163,9 @@ skip-if = true # Bug 1258809
 [browser_net_status-codes.js]
 [browser_net_streaming-response.js]
 [browser_net_throttle.js]
 [browser_net_thumbnail-click.js]
 [browser_net_timeline_ticks.js]
 skip-if = true # TODO: fix the test
 [browser_net_timing-division.js]
 [browser_net_truncate.js]
-[browser_net_persistent_logs.js]
+[browser_net_waterfall-click.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_waterfall-click.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test that clicking on the waterfall opens the timing sidebar panel.
+ */
+
+add_task(function* () {
+  let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+  let { document } = monitor.panelWin;
+
+  yield performRequestsAndWait();
+
+  let wait = waitForDOM(document, "#timings-panel");
+  let timing = document.querySelectorAll(".requests-list-timings")[0];
+
+  info("Clicking waterfall and waiting for panel update.");
+  EventUtils.synthesizeMouseAtCenter(timing, {}, monitor.panelWin);
+
+  yield wait;
+
+  ok(document.querySelector("#timings-tab[aria-selected=true]"),
+     "Timings tab is selected.");
+
+  return teardown(monitor);
+
+  function* performRequestsAndWait() {
+    let onAllEvents = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
+    yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+      content.wrappedJSObject.performRequests();
+    });
+    yield onAllEvents;
+  }
+});
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -18,16 +18,17 @@
 #include "imgINotificationObserver.h"
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/Value.h"
 #include "Layers.h"
 #include "MediaDecoder.h"
+#include "nsAppRunner.h"
 // nsNPAPIPluginInstance must be included before nsIDocument.h, which is included in mozAutoDocUpdate.h.
 #include "nsNPAPIPluginInstance.h"
 #include "gfxDrawable.h"
 #include "gfxPrefs.h"
 #include "ImageOps.h"
 #include "mozAutoDocUpdate.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
@@ -299,18 +300,20 @@ bool nsContentUtils::sAnimationsAPICoreE
 bool nsContentUtils::sAnimationsAPIElementAnimateEnabled = false;
 bool nsContentUtils::sGetBoxQuadsEnabled = false;
 bool nsContentUtils::sSkipCursorMoveForSameValueSet = false;
 bool nsContentUtils::sRequestIdleCallbackEnabled = false;
 bool nsContentUtils::sLowerNetworkPriority = false;
 #ifndef RELEASE_OR_BETA
 bool nsContentUtils::sBypassCSSOMOriginCheck = false;
 #endif
+
 bool nsContentUtils::sIsBytecodeCacheEnabled = false;
 int32_t nsContentUtils::sBytecodeCacheStrategy = 0;
+nsCString* nsContentUtils::sJSBytecodeMimeType = nullptr;
 
 int32_t nsContentUtils::sPrivacyMaxInnerWidth = 1000;
 int32_t nsContentUtils::sPrivacyMaxInnerHeight = 1000;
 
 nsContentUtils::UserInteractionObserver*
 nsContentUtils::sUserInteractionObserver = nullptr;
 
 uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
@@ -728,16 +731,19 @@ nsContentUtils::Init()
                                "privacy.trackingprotection.lower_network_priority", false);
 
   Preferences::AddBoolVarCache(&sIsBytecodeCacheEnabled,
                                "dom.script_loader.bytecode_cache.enabled", false);
 
   Preferences::AddIntVarCache(&sBytecodeCacheStrategy,
                               "dom.script_loader.bytecode_cache.strategy", 0);
 
+  nsDependentCString buildID(mozilla::PlatformBuildID());
+  sJSBytecodeMimeType = new nsCString(NS_LITERAL_CSTRING("javascript/moz-bytecode-") + buildID);
+
   Element::InitCCCallbacks();
 
   Unused << nsRFPService::GetOrCreate();
 
   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -2132,16 +2138,19 @@ nsContentUtils::Shutdown()
   sMetaText = nullptr;
   delete sOSText;
   sOSText = nullptr;
   delete sAltText;
   sAltText = nullptr;
   delete sModifierSeparator;
   sModifierSeparator = nullptr;
 
+  delete sJSBytecodeMimeType;
+  sJSBytecodeMimeType = nullptr;
+
   NS_IF_RELEASE(sSameOriginChecker);
 
   if (sUserInteractionObserver) {
     sUserInteractionObserver->Shutdown();
     NS_RELEASE(sUserInteractionObserver);
   }
 
   HTMLInputElement::Shutdown();
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2988,16 +2988,20 @@ public:
   // Check pref "dom.script_loader.bytecode_cache.enabled" to see
   // if we want to cache JS bytecode on the cache entry.
   static bool IsBytecodeCacheEnabled() { return sIsBytecodeCacheEnabled; }
 
   // Check pref "dom.script_loader.bytecode_cache.strategy" to see which
   // heuristic strategy should be used to trigger the caching of the bytecode.
   static int32_t BytecodeCacheStrategy() { return sBytecodeCacheStrategy; }
 
+  // Alternate data MIME type used by the ScriptLoader to register and read
+  // bytecode out of the nsCacheInfoChannel.
+  static nsCString& JSBytecodeMimeType() { return *sJSBytecodeMimeType; }
+
   /**
    * Checks if the passed-in name should override an existing name on the
    * window. Values which should not override include: "", "_blank", "_top",
    * "_parent" and "_self".
    */
   static bool IsOverridingWindowName(const nsAString& aName);
 
 private:
@@ -3152,16 +3156,20 @@ private:
 
   static nsString* sShiftText;
   static nsString* sControlText;
   static nsString* sMetaText;
   static nsString* sOSText;
   static nsString* sAltText;
   static nsString* sModifierSeparator;
 
+  // Alternate data mime type, used by the ScriptLoader to register and read the
+  // bytecode out of the nsCacheInfoChannel.
+  static nsCString* sJSBytecodeMimeType;
+
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
   static bool sDOMWindowDumpEnabled;
 #endif
   static bool sDoNotTrackEnabled;
   static mozilla::LazyLogModule sDOMDumpLog;
 };
 
 /* static */ inline
--- a/dom/script/ScriptLoadHandler.cpp
+++ b/dom/script/ScriptLoadHandler.cpp
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ScriptLoadHandler.h"
 #include "ScriptLoader.h"
 #include "ScriptTrace.h"
 
+#include "nsContentUtils.h"
+
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
 namespace dom {
 
 #undef LOG
 #define LOG(args) \
@@ -272,20 +274,21 @@ ScriptLoadHandler::EnsureKnownDataType(n
   nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
   MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
   if (cic) {
     nsAutoCString altDataType;
     cic->GetAlternativeDataType(altDataType);
-    if (altDataType.EqualsLiteral("javascript/moz-bytecode-" NS_STRINGIFY(MOZ_BUILDID))) {
+    if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
       mRequest->mDataType = ScriptLoadRequest::DataType::Bytecode;
       TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_bytecode");
     } else {
+      MOZ_ASSERT(altDataType.IsEmpty());
       mRequest->mDataType = ScriptLoadRequest::DataType::Source;
       TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
     }
   } else {
     mRequest->mDataType = ScriptLoadRequest::DataType::Source;
     TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
   }
   MOZ_ASSERT(!mRequest->IsUnknownDataType());
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -72,20 +72,19 @@ namespace mozilla {
 namespace dom {
 
 LazyLogModule ScriptLoader::gCspPRLog("CSP");
 LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
 
 #define LOG(args) \
   MOZ_LOG(gScriptLoaderLog, mozilla::LogLevel::Debug, args)
 
-// These are the Alternate Data MIME type used by the ScriptLoader to
-// register and read bytecode out of the nsCacheInfoChannel.
-static NS_NAMED_LITERAL_CSTRING(
-  kBytecodeMimeType, "javascript/moz-bytecode-" NS_STRINGIFY(MOZ_BUILDID));
+
+// Alternate Data MIME type used by the ScriptLoader to register that we want to
+// store bytecode without reading it.
 static NS_NAMED_LITERAL_CSTRING(kNullMimeType, "javascript/null");
 
 //////////////////////////////////////////////////////////////
 // ScriptLoader::PreloadInfo
 //////////////////////////////////////////////////////////////
 
 inline void
 ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField)
@@ -904,26 +903,26 @@ ScriptLoader::StartLoad(ScriptLoadReques
                               loadGroup,
                               prompter,
                               nsIRequest::LOAD_NORMAL |
                               nsIChannel::LOAD_CLASSIFY_URI);
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   // To avoid decoding issues, the JSVersion is explicitly guarded here, and the
-  // build-id is part of the kBytecodeMimeType constant.
+  // build-id is part of the JSBytecodeMimeType constant.
   aRequest->mCacheInfo = nullptr;
   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
   if (cic && nsContentUtils::IsBytecodeCacheEnabled() &&
       aRequest->mJSVersion == JSVERSION_DEFAULT) {
     if (!aRequest->IsLoadingSource()) {
       // Inform the HTTP cache that we prefer to have information coming from the
       // bytecode cache instead of the sources, if such entry is already registered.
       LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
-      cic->PreferAlternativeDataType(kBytecodeMimeType);
+      cic->PreferAlternativeDataType(nsContentUtils::JSBytecodeMimeType());
     } else {
       // If we are explicitly loading from the sources, such as after a
       // restarted request, we might still want to save the bytecode after.
       //
       // The following tell the cache to look for an alternative data type which
       // does not exist, such that we can later save the bytecode with a
       // different alternative data type.
       LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest));
@@ -1984,25 +1983,25 @@ ScriptLoader::ShouldCacheBytecode(Script
 
   // Check that the cache entry got accessed recently, before caching it.
   if (hasTimeSinceLastFetched) {
     uint32_t lastFetched = 0;
     if (NS_FAILED(aRequest->mCacheInfo->GetCacheTokenLastFetched(&lastFetched))) {
       LOG(("ScriptLoadRequest (%p): Bytecode-cache: Cannot get lastFetched.", aRequest));
       return false;
     }
-    TimeStamp now = TimeStamp::NowLoRes();
-    TimeStamp last = TimeStamp() + TimeDuration::FromSeconds(lastFetched);
-    if (now < last) {
+    uint32_t now = PR_Now() / PR_USEC_PER_SEC;
+    if (now < lastFetched) {
       LOG(("ScriptLoadRequest (%p): Bytecode-cache: (What?) lastFetched set in the future.", aRequest));
       return false;
     }
+    TimeDuration since = TimeDuration::FromSeconds(now - lastFetched);
     LOG(("ScriptLoadRequest (%p): Bytecode-cache: lastFetched = %f sec. ago.",
-         aRequest, (now - last).ToSeconds()));
-    if (now - last >= timeSinceLastFetched) {
+         aRequest, since.ToSeconds()));
+    if (since >= timeSinceLastFetched) {
       return false;
     }
   }
 
   LOG(("ScriptLoadRequest (%p): Bytecode-cache: Trigger encoding.", aRequest));
   return true;
 }
 
@@ -2259,17 +2258,17 @@ ScriptLoader::EncodeRequestBytecode(JSCo
          aRequest));
     return;
   }
 
   // Open the output stream to the cache entry alternate data storage. This
   // might fail if the stream is already open by another request, in which
   // case, we just ignore the current one.
   nsCOMPtr<nsIOutputStream> output;
-  rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(kBytecodeMimeType,
+  rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(nsContentUtils::JSBytecodeMimeType(),
                                                          getter_AddRefs(output));
   if (NS_FAILED(rv)) {
     LOG(("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output = %p)",
          aRequest, unsigned(rv), output.get()));
     return;
   }
   MOZ_ASSERT(output);
   auto closeOutStream = mozilla::MakeScopeExit([&]() {
--- a/extensions/cookie/test/unit/test_cookies_async_failure.js
+++ b/extensions/cookie/test/unit/test_cookies_async_failure.js
@@ -206,20 +206,22 @@ function* run_test_1(generator)
   do_check_false(do_get_backup_file(profile).exists());
   do_run_generator(generator);
 }
 
 function* run_test_2(generator)
 {
   // Load the profile and populate it.
   do_load_profile();
-  for (let i = 0; i < 3000; ++i) {
-    let uri = NetUtil.newURI("http://" + i + ".com/");
-    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
-  }
+  Services.cookies.runInTransaction(_=>{
+    for (let i = 0; i < 3000; ++i) {
+      let uri = NetUtil.newURI("http://" + i + ".com/");
+      Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+    }
+  });
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Corrupt the database file.
   let size = do_corrupt_db(do_get_cookie_file(profile));
 
@@ -276,26 +278,28 @@ function* run_test_2(generator)
 function* run_test_3(generator)
 {
   // Set the maximum cookies per base domain limit to a large value, so that
   // corrupting the database is easier.
   Services.prefs.setIntPref("network.cookie.maxPerHost", 3000);
 
   // Load the profile and populate it.
   do_load_profile();
-  for (let i = 0; i < 10; ++i) {
-    let uri = NetUtil.newURI("http://hither.com/");
-    Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
-      null);
-  }
-  for (let i = 10; i < 3000; ++i) {
-    let uri = NetUtil.newURI("http://haithur.com/");
-    Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
-      null);
-  }
+  Services.cookies.runInTransaction(_=>{
+    for (let i = 0; i < 10; ++i) {
+      let uri = NetUtil.newURI("http://hither.com/");
+      Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
+        null);
+    }
+    for (let i = 10; i < 3000; ++i) {
+      let uri = NetUtil.newURI("http://haithur.com/");
+      Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
+        null);
+    }
+  });
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Corrupt the database file.
   let size = do_corrupt_db(do_get_cookie_file(profile));
 
@@ -367,88 +371,22 @@ function* run_test_3(generator)
   do_check_false(do_get_backup_file(profile).exists());
   do_run_generator(generator);
 }
 
 function* run_test_4(generator)
 {
   // Load the profile and populate it.
   do_load_profile();
-  for (let i = 0; i < 3000; ++i) {
-    let uri = NetUtil.newURI("http://" + i + ".com/");
-    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
-  }
-
-  // Close the profile.
-  do_close_profile(sub_generator);
-  yield;
-
-  // Corrupt the database file.
-  let size = do_corrupt_db(do_get_cookie_file(profile));
-
-  // Load the profile.
-  do_load_profile();
-
-  // At this point, the database connection should be open. Ensure that it
-  // succeeded.
-  do_check_false(do_get_backup_file(profile).exists());
-
-  // Synchronously read in the first cookie. This will cause it to go into the
-  // cookie table, whereupon it will be written out during database rebuild.
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
-
-  // Queue up an INSERT for the same base domain. This should also go into
-  // memory and be written out during database rebuild.
-  let uri = NetUtil.newURI("http://0.com/");
-  Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
-
-  // Wait for the asynchronous read to choke and the insert to fail shortly
-  // thereafter, at which point the backup file will be created and the database
-  // rebuilt.
-  new _observer(sub_generator, "cookie-db-rebuilding");
-  yield;
-  do_execute_soon(function() { do_run_generator(sub_generator); });
-  yield;
-
-  // Close the profile.
-  do_close_profile(sub_generator);
-  yield;
-
-  // Check that the original database was renamed.
-  do_check_true(do_get_backup_file(profile).exists());
-  do_check_eq(do_get_backup_file(profile).fileSize, size);
-  let db = Services.storage.openDatabase(do_get_cookie_file(profile));
-  do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
-  db.close();
-
-  // Load the profile, and check that it contains the new cookie.
-  do_load_profile();
-  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
-  do_check_eq(do_count_cookies(), 2);
-
-  // Close the profile.
-  do_close_profile(sub_generator);
-  yield;
-
-  // Clean up.
-  do_get_cookie_file(profile).remove(false);
-  do_get_backup_file(profile).remove(false);
-  do_check_false(do_get_cookie_file(profile).exists());
-  do_check_false(do_get_backup_file(profile).exists());
-  do_run_generator(generator);
-}
-
-function* run_test_4(generator)
-{
-  // Load the profile and populate it.
-  do_load_profile();
-  for (let i = 0; i < 3000; ++i) {
-    let uri = NetUtil.newURI("http://" + i + ".com/");
-    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
-  }
+  Services.cookies.runInTransaction(_=>{
+    for (let i = 0; i < 3000; ++i) {
+      let uri = NetUtil.newURI("http://" + i + ".com/");
+      Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+    }
+  });
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Corrupt the database file.
   let size = do_corrupt_db(do_get_cookie_file(profile));
 
@@ -507,23 +445,25 @@ function* run_test_4(generator)
   do_check_false(do_get_backup_file(profile).exists());
   do_run_generator(generator);
 }
 
 function* run_test_5(generator)
 {
   // Load the profile and populate it.
   do_load_profile();
-  let uri = NetUtil.newURI("http://bar.com/");
-  Services.cookies.setCookieString(uri, null, "oh=hai; path=/; max-age=1000",
-    null);
-  for (let i = 0; i < 3000; ++i) {
-    let uri = NetUtil.newURI("http://" + i + ".com/");
-    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
-  }
+  Services.cookies.runInTransaction(_=>{
+    let uri = NetUtil.newURI("http://bar.com/");
+    Services.cookies.setCookieString(uri, null, "oh=hai; path=/; max-age=1000",
+      null);
+    for (let i = 0; i < 3000; ++i) {
+      let uri = NetUtil.newURI("http://" + i + ".com/");
+      Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+    }
+  });
 
   // Close the profile.
   do_close_profile(sub_generator);
   yield;
 
   // Corrupt the database file.
   let size = do_corrupt_db(do_get_cookie_file(profile));
 
--- a/gfx/2d/DrawTarget.cpp
+++ b/gfx/2d/DrawTarget.cpp
@@ -6,16 +6,17 @@
 #include "2D.h"
 #include "Logging.h"
 #include "PathHelpers.h"
 
 #include "DrawTargetCapture.h"
 
 #ifdef BUILD_ARM_NEON
 #include "mozilla/arm.h"
+#include "LuminanceNEON.h"
 #endif
 
 namespace mozilla {
 namespace gfx {
 
 /**
  * Byte offsets of channels in a native packed gfxColor or cairo image surface.
  */
@@ -71,16 +72,25 @@ 239, 242, 244, 246, 248, 250, 253, 255
 static void
 ComputesRGBLuminanceMask(const uint8_t *aSourceData,
                          int32_t aSourceStride,
                          uint8_t *aDestData,
                          int32_t aDestStride,
                          const IntSize &aSize,
                          float aOpacity)
 {
+#ifdef BUILD_ARM_NEON
+  if (mozilla::supports_neon()) {
+    ComputesRGBLuminanceMask_NEON(aSourceData, aSourceStride,
+                                  aDestData, aDestStride,
+                                  aSize, aOpacity);
+    return;
+  }
+#endif
+
   int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
   int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
   int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
   int32_t sourceOffset = aSourceStride - 4 * aSize.width;
   const uint8_t *sourcePixel = aSourceData;
   int32_t destOffset = aDestStride - aSize.width;
   uint8_t *destPixel = aDestData;
 
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -111,17 +111,16 @@ DrawTargetD2D1::EnsureLuminanceEffect()
   if (FAILED(hr)) {
     gfxWarning() << "Failed to create luminance effect. Code: " << hexa(hr);
   }
 }
 
 already_AddRefed<SourceSurface>
 DrawTargetD2D1::IntoLuminanceSource(LuminanceType aLuminanceType, float aOpacity)
 {
-  //return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
   if (aLuminanceType != LuminanceType::LUMINANCE) {
     return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity);
   }
 
   // Create the luminance effect
   EnsureLuminanceEffect();
   mLuminanceEffect->SetInput(0, mBitmap);
 
new file mode 100644
--- /dev/null
+++ b/gfx/2d/LuminanceNEON.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 <arm_neon.h>
+#include "LuminanceNEON.h"
+
+using namespace mozilla::gfx;
+
+/**
+ * Byte offsets of channels in a native packed gfxColor or cairo image surface.
+ */
+#ifdef IS_BIG_ENDIAN
+#define GFX_ARGB32_OFFSET_A 0
+#define GFX_ARGB32_OFFSET_R 1
+#define GFX_ARGB32_OFFSET_G 2
+#define GFX_ARGB32_OFFSET_B 3
+#else
+#define GFX_ARGB32_OFFSET_A 3
+#define GFX_ARGB32_OFFSET_R 2
+#define GFX_ARGB32_OFFSET_G 1
+#define GFX_ARGB32_OFFSET_B 0
+#endif
+
+
+void
+ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData,
+                              int32_t aSourceStride,
+                              uint8_t *aDestData,
+                              int32_t aDestStride,
+                              const IntSize &aSize,
+                              float aOpacity)
+{
+  int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+  int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+  int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+  const uint8_t *sourcePixel = aSourceData;
+  int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+  uint8_t *destPixel = aDestData;
+  int32_t destOffset = aDestStride - aSize.width;
+
+  sourcePixel = aSourceData;
+  int32_t remainderWidth = aSize.width % 8;
+  int32_t roundedWidth = aSize.width - remainderWidth;
+  uint16x8_t temp;
+  uint8x8_t gray;
+  uint8x8_t redVector = vdup_n_u8(redFactor);
+  uint8x8_t greenVector = vdup_n_u8(greenFactor);
+  uint8x8_t blueVector = vdup_n_u8(blueFactor);
+  uint8x8_t fullBitVector = vdup_n_u8(255);
+  uint8x8_t oneVector = vdup_n_u8(1);
+  for (int32_t y = 0; y < aSize.height; y++) {
+    // Calculate luminance by neon with 8 pixels per loop
+    for (int32_t x = 0; x < roundedWidth; x += 8) {
+      uint8x8x4_t argb  = vld4_u8(sourcePixel);
+      temp = vmull_u8(argb.val[GFX_ARGB32_OFFSET_R], redVector); // temp = red * redFactor
+      temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_G], greenVector); // temp += green * greenFactor
+      temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_B], blueVector); // temp += blue * blueFactor
+      gray = vshrn_n_u16(temp, 8); // gray = temp >> 8
+
+      // Check alpha value
+      uint8x8_t alphaVector = vtst_u8(argb.val[GFX_ARGB32_OFFSET_A], fullBitVector);
+      gray = vmul_u8(gray, vand_u8(alphaVector, oneVector));
+
+      // Put the result to the 8 pixels
+      vst1_u8(destPixel, gray);
+      sourcePixel += 8 * 4;
+      destPixel += 8;
+    }
+
+    // Calculate the rest pixels of the line by cpu
+    for (int32_t x = 0; x < remainderWidth; x++) {
+      if (sourcePixel[GFX_ARGB32_OFFSET_A] > 0) {
+        *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R]+
+                      greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
+                      blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
+      } else {
+        *destPixel = 0;
+      }
+      sourcePixel += 4;
+      destPixel++;
+    }
+    sourcePixel += sourceOffset;
+    destPixel += destOffset;
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/gfx/2d/LuminanceNEON.h
@@ -0,0 +1,19 @@
+/* -*- 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 __LUMINANCENEON_H__
+#define __LUMINANCENEON_H__
+
+#include "mozilla/gfx/Point.h"
+
+void
+ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData,
+                              int32_t aSourceStride,
+                              uint8_t *aDestData,
+                              int32_t aDestStride,
+                              const mozilla::gfx::IntSize &aSize,
+                              float aOpacity);
+
+#endif /* __LUMINANCENEON_H__ */
--- a/gfx/2d/moz.build
+++ b/gfx/2d/moz.build
@@ -215,19 +215,21 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
     SOURCES += [
         'MacIOSurface.cpp',
         'QuartzSupport.mm',
     ]
 
 if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']:
     SOURCES += [
         'BlurNEON.cpp',
+        'LuminanceNEON.cpp',
         'SwizzleNEON.cpp',
     ]
     SOURCES['BlurNEON.cpp'].flags += CONFIG['NEON_FLAGS']
+    SOURCES['LuminanceNEON.cpp'].flags += CONFIG['NEON_FLAGS']
     SOURCES['SwizzleNEON.cpp'].flags += CONFIG['NEON_FLAGS']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 for var in ('USE_CAIRO', 'MOZ2D_HAS_MOZ_CAIRO'):
     DEFINES[var] = True
--- a/layout/svg/nsSVGMaskFrame.cpp
+++ b/layout/svg/nsSVGMaskFrame.cpp
@@ -119,23 +119,23 @@ nsSVGMaskFrame::GetMaskForMaskedFrame(Ma
     gfxMatrix m = mMatrixForChildren;
     if (kid->GetContent()->IsSVGElement()) {
       m = static_cast<nsSVGElement*>(kid->GetContent())->
             PrependLocalTransformsTo(m, eUserSpaceToParent);
     }
     nsSVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m, aParams.imgParams);
   }
 
-  if (StyleSVG()->mColorInterpolation ==
-    NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) {
-    maskType = NS_STYLE_COLOR_INTERPOLATION_LINEARRGB;
-  }
-
   RefPtr<SourceSurface> surface;
   if (maskType == NS_STYLE_MASK_TYPE_LUMINANCE) {
+    if (StyleSVG()->mColorInterpolation ==
+        NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) {
+      maskType = NS_STYLE_COLOR_INTERPOLATION_LINEARRGB;
+    }
+
     RefPtr<SourceSurface> maskSnapshot =
       maskDT->IntoLuminanceSource(GetLuminanceType(maskType),
                                   aParams.opacity);
     if (!maskSnapshot) {
       return nullptr;
     }
     surface = maskSnapshot.forget();
   } else {
--- a/netwerk/cookie/CookieServiceChild.cpp
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -239,11 +239,17 @@ CookieServiceChild::SetCookieStringFromH
                                             const char *aCookieString,
                                             const char *aServerTime,
                                             nsIChannel *aChannel) 
 {
   return SetCookieStringInternal(aHostURI, aChannel, aCookieString,
                                  aServerTime, true);
 }
 
+NS_IMETHODIMP
+CookieServiceChild::RunInTransaction(nsICookieTransactionCallback* aCallback)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -2261,16 +2261,37 @@ nsCookieService::CreatePurgeList(nsICook
   nsCOMPtr<nsIMutableArray> removedList =
     do_CreateInstance(NS_ARRAY_CONTRACTID);
   removedList->AppendElement(aCookie, false);
   return removedList.forget();
 }
 
 /******************************************************************************
  * nsCookieService:
+ * public transaction helper impl
+ ******************************************************************************/
+
+NS_IMETHODIMP
+nsCookieService::RunInTransaction(nsICookieTransactionCallback* aCallback)
+{
+  NS_ENSURE_ARG(aCallback);
+  if (NS_WARN_IF(!mDefaultDBState->dbConn)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  mozStorageTransaction transaction(mDefaultDBState->dbConn, true);
+
+  if (NS_FAILED(aCallback->Callback())) {
+    Unused << transaction.Rollback();
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+/******************************************************************************
+ * nsCookieService:
  * pref observer impl
  ******************************************************************************/
 
 void
 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch)
 {
   int32_t val;
   if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val)))
--- a/netwerk/cookie/nsICookieService.idl
+++ b/netwerk/cookie/nsICookieService.idl
@@ -5,16 +5,25 @@
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIPrompt;
 interface nsIChannel;
 
 /**
+ * @see nsICookieService::runInTransaction
+ */
+[scriptable, function, uuid(0fc41ffb-f1b7-42d9-9a42-8dc420c158c1)]
+interface nsICookieTransactionCallback : nsISupports
+{
+  void callback();
+};
+
+/**
  * nsICookieService
  *
  * Provides methods for setting and getting cookies in the context of a
  * page load.  See nsICookieManager for methods to manipulate the cookie
  * database directly.  This separation of interface is mainly historical.
  *
  * This service broadcasts the notifications detailed below when the cookie
  * list is changed, or a cookie is rejected.
@@ -185,9 +194,24 @@ interface nsICookieService : nsISupports
    * @param aChannel
    *        the channel used to load the document.  this parameter should not
    *        be null, otherwise the cookies will not be set if third-party
    *        cookies have been disabled by the user. (the channel is used
    *        to determine the originating URI of the document; if it is not
    *        provided, the cookies will be assumed third-party.)
    */
   void setCookieStringFromHttp(in nsIURI aURI, in nsIURI aFirstURI, in nsIPrompt aPrompt, in string aCookie, in string aServerTime, in nsIChannel aChannel);
+
+
+  /*
+   * Batch SQLite operations into one transaction. By default each call to
+   * CookieService that affects the underlying SQLite database (add, remove,
+   * setCookieString etc.) runs in a separate transaction.  If you do this many
+   * times in a row, it's faster and suggested to wrap them all in a single
+   * transaction by setting all the operations into the callback parameter.
+   * Example: test scripts that need to construct a large cookie database.
+   * @param aCallback
+   *        nsICookieTransactionCallback interface to call
+   * @throws NS_ERROR_FAILURE if aCallback() fails.
+   * @throws NS_ERROR_NOT_AVAILABLE if the connection is not established.
+   */
+   void runInTransaction(in nsICookieTransactionCallback aCallback);
 };
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2680,29 +2680,29 @@ HttpChannelChild::SetupFallbackChannel(c
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsICacheInfoChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::GetCacheTokenFetchCount(int32_t *_retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
-  if (!mCacheEntryAvailable || !mAltDataCacheEntryAvailable) {
+  if (!mCacheEntryAvailable && !mAltDataCacheEntryAvailable) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   *_retval = mCacheFetchCount;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpChannelChild::GetCacheTokenLastFetched(uint32_t *_retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
-  if (!mCacheEntryAvailable || !mAltDataCacheEntryAvailable) {
+  if (!mCacheEntryAvailable && !mAltDataCacheEntryAvailable) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   *_retval = mCacheLastFetched;
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/toolkit/components/aboutcache/content/aboutCache.js
+++ b/toolkit/components/aboutcache/content/aboutCache.js
@@ -1,17 +1,17 @@
 /* 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/. */
 
 // First, parse and save the incoming arguments ("?storage=name&context=key")
 // Note: window.location.search doesn't work with nsSimpleURIs used for about:* addresses.
 var search = window.location.href.match(/^.*\?(.*)$/);
 var searchParams = new URLSearchParams(search ? search[1] : "");
-var storage = searchParams.get("storage");
+var storage = searchParams.get("storage") || "";
 var cacheContext = searchParams.get("context");
 
 // The context is in a format as used by the HTTP cache v2 back end
 if (cacheContext)
   var [context, isAnon, isInBrowser, appId, isPrivate] = cacheContext.match(/(a,)?(b,)?(i\d+,)?(p,)?/);
 if (appId)
   appId = appId.match(/i(\d+),/)[1];
 
--- a/toolkit/components/xulstore/XULStore.js
+++ b/toolkit/components/xulstore/XULStore.js
@@ -16,17 +16,16 @@ const WRITE_DELAY_MS = (debugMode ? 3 : 
 
 const XULSTORE_CONTRACTID = "@mozilla.org/xul/xulstore;1";
 const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}");
 const STOREDB_FILENAME = "xulstore.json";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 function XULStore() {
   if (!Services.appinfo.inSafeMode)
     this.load();
 }
 
 XULStore.prototype = {
@@ -60,21 +59,17 @@ XULStore.prototype = {
   _writeTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
 
   load() {
     Services.obs.addObserver(this, "profile-before-change", true);
 
     this._storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
     this._storeFile.append(STOREDB_FILENAME);
 
-    if (!this._storeFile.exists()) {
-      this.import();
-    } else {
-      this.readFile();
-    }
+    this.readFile();
   },
 
   observe(subject, topic, data) {
     this.writeFile();
     if (topic == "profile-before-change") {
       this._saveAllowed = false;
     }
   },
@@ -84,81 +79,30 @@ XULStore.prototype = {
    */
   log(message) {
     if (!debugMode)
       return;
     dump("XULStore: " + message + "\n");
     Services.console.logStringMessage("XULStore: " + message);
   },
 
-  import() {
-    let localStoreFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
-
-    localStoreFile.append("localstore.rdf");
-    if (!localStoreFile.exists()) {
-      return;
-    }
-
-    const RDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
-    const persistKey = RDF.GetResource("http://home.netscape.com/NC-rdf#persist");
-
-    this.log("Import localstore from " + localStoreFile.path);
-
-    let localStoreURI = Services.io.newFileURI(localStoreFile).spec;
-    let localStore = RDF.GetDataSourceBlocking(localStoreURI);
-    let resources = localStore.GetAllResources();
-
-    while (resources.hasMoreElements()) {
-      let resource = resources.getNext().QueryInterface(Ci.nsIRDFResource);
-      let uri;
-
-      try {
-        uri = NetUtil.newURI(resource.ValueUTF8);
-      } catch (ex) {
-        continue; // skip invalid uris
-      }
-
-      // If this has a ref, then this is an attribute reference. Otherwise,
-      // this is a document reference.
-      if (!uri.hasRef)
-          continue;
-
-      // Verify that there the persist key is connected up.
-      let docURI = uri.specIgnoringRef;
-
-      if (!localStore.HasAssertion(RDF.GetResource(docURI), persistKey, resource, true))
-          continue;
-
-      let id = uri.ref;
-      let attrs = localStore.ArcLabelsOut(resource);
-
-      while (attrs.hasMoreElements()) {
-        let attr = attrs.getNext().QueryInterface(Ci.nsIRDFResource);
-        let value = localStore.GetTarget(resource, attr, true);
-
-        if (value instanceof Ci.nsIRDFLiteral) {
-          this.setValue(docURI, id, attr.ValueUTF8, value.Value);
-        }
-      }
-    }
-  },
-
   readFile() {
     const MODE_RDONLY = 0x01;
     const FILE_PERMS  = 0o600;
 
     let stream = Cc["@mozilla.org/network/file-input-stream;1"].
                  createInstance(Ci.nsIFileInputStream);
     let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
     try {
       stream.init(this._storeFile, MODE_RDONLY, FILE_PERMS, 0);
       this._data = json.decodeFromStream(stream, stream.available());
     } catch (e) {
       this.log("Error reading JSON: " + e);
-      // Ignore problem, we'll just continue on with an empty dataset.
+      // This exception could mean that the file didn't exist.
+      // We'll just ignore the error and start with a blank slate.
     } finally {
       stream.close();
     }
   },
 
   async writeFile() {
     if (!this._needsSaving)
       return;
--- a/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js
+++ b/toolkit/components/xulstore/tests/xpcshell/test_XULStore.js
@@ -17,17 +17,17 @@ var aboutURI = "about:config";
 
 function run_test() {
   do_get_profile();
   run_next_test();
 }
 
 function checkValue(uri, id, attr, reference) {
   let value = XULStore.getValue(uri, id, attr);
-  do_check_true(value === reference);
+  do_check_eq(value, reference);
 }
 
 function checkValueExists(uri, id, attr, exists) {
   do_check_eq(XULStore.hasValue(uri, id, attr), exists);
 }
 
 function getIDs(uri) {
   let it = XULStore.getIDsEnumerator(uri);
@@ -54,52 +54,55 @@ function getAttributes(uri, id) {
 
   result.sort();
   return result;
 }
 
 function checkArrays(a, b) {
   a.sort();
   b.sort();
-  do_check_true(a.toString() == b.toString());
+  do_check_eq(a.toString(), b.toString());
 }
 
 function checkOldStore() {
-  checkArrays(["addon-bar", "main-window", "sidebar-title"], getIDs(browserURI));
-  checkArrays(["collapsed"], getAttributes(browserURI, "addon-bar"));
-  checkArrays(["height", "screenX", "screenY", "sizemode", "width"],
+  checkArrays([], getIDs(browserURI));
+  checkArrays([], getAttributes(browserURI, "addon-bar"));
+  checkArrays([],
               getAttributes(browserURI, "main-window"));
-  checkArrays(["value"], getAttributes(browserURI, "sidebar-title"));
+  checkArrays([], getAttributes(browserURI, "sidebar-title"));
 
-  checkValue(browserURI, "addon-bar", "collapsed", "true");
-  checkValue(browserURI, "main-window", "width", "994");
-  checkValue(browserURI, "main-window", "height", "768");
-  checkValue(browserURI, "main-window", "screenX", "4");
-  checkValue(browserURI, "main-window", "screenY", "22");
-  checkValue(browserURI, "main-window", "sizemode", "normal");
+  checkValue(browserURI, "addon-bar", "collapsed", "");
+  checkValue(browserURI, "main-window", "width", "");
+  checkValue(browserURI, "main-window", "height", "");
+  checkValue(browserURI, "main-window", "screenX", "");
+  checkValue(browserURI, "main-window", "screenY", "");
+  checkValue(browserURI, "main-window", "sizemode", "");
   checkValue(browserURI, "sidebar-title", "value", "");
 
-  checkArrays(["lockCol", "prefCol"], getIDs(aboutURI));
-  checkArrays(["ordinal"], getAttributes(aboutURI, "lockCol"));
-  checkArrays(["ordinal", "sortDirection"], getAttributes(aboutURI, "prefCol"));
+  checkArrays([], getIDs(aboutURI));
+  checkArrays([], getAttributes(aboutURI, "lockCol"));
+  checkArrays([], getAttributes(aboutURI, "prefCol"));
 
-  checkValue(aboutURI, "prefCol", "ordinal", "1");
-  checkValue(aboutURI, "prefCol", "sortDirection", "ascending");
-  checkValue(aboutURI, "lockCol", "ordinal", "3");
+  checkValue(aboutURI, "prefCol", "ordinal", "");
+  checkValue(aboutURI, "prefCol", "sortDirection", "");
+  checkValue(aboutURI, "lockCol", "ordinal", "");
 }
 
 add_task(async function testImport() {
   let src = "localstore.rdf";
   let dst = OS.Path.join(OS.Constants.Path.profileDir, src);
 
   await OS.File.copy(src, dst);
 
-  // Importing relies on XULStore not yet being loaded before this point.
+  // Test to make sure that localstore.rdf isn't imported any more.
   XULStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
   checkOldStore();
+
+  // Set a value that a future test depends on manually
+  XULStore.setValue(browserURI, "main-window", "width", "994");
 });
 
 add_task(async function testTruncation() {
   let dos = Array(8192).join("~");
   // Long id names should trigger an exception
   Assert.throws(() => XULStore.setValue(browserURI, dos, "foo", "foo"), /NS_ERROR_ILLEGAL_VALUE/);
 
   // Long attr names should trigger an exception
@@ -128,48 +131,48 @@ add_task(async function testHasValue() {
   checkValueExists(browserURI, "main-window", "width", true);
 });
 
 add_task(async function testSetValue() {
   // Set new attribute
   checkValue(browserURI, "side-bar", "width", "");
   XULStore.setValue(browserURI, "side-bar", "width", "1000");
   checkValue(browserURI, "side-bar", "width", "1000");
-  checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI));
+  checkArrays(["main-window", "side-bar"], getIDs(browserURI));
   checkArrays(["width"], getAttributes(browserURI, "side-bar"));
 
   // Modify existing property
   checkValue(browserURI, "side-bar", "width", "1000");
   XULStore.setValue(browserURI, "side-bar", "width", "1024");
   checkValue(browserURI, "side-bar", "width", "1024");
-  checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI));
+  checkArrays(["main-window", "side-bar"], getIDs(browserURI));
   checkArrays(["width"], getAttributes(browserURI, "side-bar"));
 
   // Add another attribute
   checkValue(browserURI, "side-bar", "height", "");
   XULStore.setValue(browserURI, "side-bar", "height", "1000");
   checkValue(browserURI, "side-bar", "height", "1000");
-  checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI));
+  checkArrays(["main-window", "side-bar"], getIDs(browserURI));
   checkArrays(["width", "height"], getAttributes(browserURI, "side-bar"));
 });
 
 add_task(async function testRemoveValue() {
   // Remove first attribute
   checkValue(browserURI, "side-bar", "width", "1024");
   XULStore.removeValue(browserURI, "side-bar", "width");
   checkValue(browserURI, "side-bar", "width", "");
   checkValueExists(browserURI, "side-bar", "width", false);
-  checkArrays(["addon-bar", "main-window", "side-bar", "sidebar-title"], getIDs(browserURI));
+  checkArrays(["main-window", "side-bar"], getIDs(browserURI));
   checkArrays(["height"], getAttributes(browserURI, "side-bar"));
 
   // Remove second attribute
   checkValue(browserURI, "side-bar", "height", "1000");
   XULStore.removeValue(browserURI, "side-bar", "height");
   checkValue(browserURI, "side-bar", "height", "");
-  checkArrays(["addon-bar", "main-window", "sidebar-title"], getIDs(browserURI));
+  checkArrays(["main-window"], getIDs(browserURI));
 
   // Removing an attribute that doesn't exists shouldn't fail
   XULStore.removeValue(browserURI, "main-window", "bar");
 
   // Removing from an id that doesn't exists shouldn't fail
   XULStore.removeValue(browserURI, "foo", "bar");
 
   // Removing from a document that doesn't exists shouldn't fail
--- a/toolkit/content/widgets/scrollbox.xml
+++ b/toolkit/content/widgets/scrollbox.xml
@@ -180,57 +180,80 @@
           var innerRect = {};
           innerRect.left = outerRect.left - this._scrollbox.scrollLeft;
           innerRect.top = outerRect.top - this._scrollbox.scrollTop;
           innerRect.right = innerRect.left + this._scrollbox.scrollWidth;
           innerRect.bottom = innerRect.top + this._scrollbox.scrollHeight;
           return innerRect;
         ]]></getter>
       </property>
-      <property name="scrollboxPaddingStart" readonly="true">
-        <getter><![CDATA[
-          var ltr = (window.getComputedStyle(this).direction == "ltr");
-          var paddingStartName = ltr ? "padding-left" : "padding-right";
-          var scrollboxStyle = window.getComputedStyle(this._scrollbox);
-          return parseFloat(scrollboxStyle.getPropertyValue(paddingStartName));
-        ]]></getter>
-      </property>
+      <field name="scrollboxPaddingStart"><![CDATA[
+        parseFloat(window.getComputedStyle(this._scrollbox)[
+          this._isRTLScrollbox ? "paddingRight" : "paddingLeft"
+        ]);
+      ]]></field>
       <property name="scrollPosition">
         <getter><![CDATA[
           return this.orient == "vertical" ?
                  this._scrollbox.scrollTop :
                  this._scrollbox.scrollLeft;
         ]]></getter>
         <setter><![CDATA[
           if (this.orient == "vertical")
             this._scrollbox.scrollTop = val;
           else
             this._scrollbox.scrollLeft = val;
           return val;
         ]]></setter>
       </property>
 
-      <property name="_startEndProps" readonly="true">
-        <getter><![CDATA[
-          return this.orient == "vertical" ?
-                 ["top", "bottom"] : ["left", "right"];
-        ]]></getter>
-      </property>
+      <field name="_startEndProps"><![CDATA[
+        this.orient == "vertical" ? ["top", "bottom"] : ["left", "right"];
+      ]]></field>
 
       <field name="_isRTLScrollbox"><![CDATA[
         this.orient != "vertical" &&
         document.defaultView.getComputedStyle(this._scrollbox).direction == "rtl";
       ]]></field>
 
       <field name="_scrollTarget">null</field>
 
+      <method name="_boundsWithoutFlushing">
+        <parameter name="element"/>
+        <body><![CDATA[
+          if (!("_DOMWindowUtils" in this)) {
+            try {
+              this._DOMWindowUtils =
+                window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
+            } catch (e) {
+              // Can't access nsIDOMWindowUtils if we're unprivileged.
+              this._DOMWindowUtils = null;
+            }
+          }
+
+          return this._DOMWindowUtils ?
+                 this._DOMWindowUtils.getBoundsWithoutFlushing(element) :
+                 element.getBoundingClientRect();
+        ]]></body>
+      </method>
+
       <method name="_canScrollToElement">
         <parameter name="element"/>
         <body><![CDATA[
-          return window.getComputedStyle(element).display != "none";
+          if (element.hidden) {
+            return false;
+          }
+
+          // See if the element is hidden via CSS without the hidden attribute.
+          // If we get only zeros for the client rect, this means the element
+          // is hidden. As a performance optimization, we don't flush layout
+          // here which means that on the fly changes aren't fully supported.
+          let rect = this._boundsWithoutFlushing(element);
+          return !!(rect.top || rect.left || rect.width || rect.height);
         ]]></body>
       </method>
 
       <method name="ensureElementIsVisible">
         <parameter name="element"/>
         <parameter name="aSmoothScroll"/>
         <body><![CDATA[
           if (!this._canScrollToElement(element))
--- a/toolkit/locales/en-US/chrome/pluginproblem/pluginproblem.dtd
+++ b/toolkit/locales/en-US/chrome/pluginproblem/pluginproblem.dtd
@@ -1,13 +1,14 @@
 <!-- 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/. -->
 
 <!-- LOCALIZATION NOTE (tapToPlayPlugin): Mobile (used for touch interfaces) only has one type of plugin possible. -->
+<!ENTITY pluginActivationWarning                             "This site uses a plugin that may slow &brandShortName;.">
 <!ENTITY tapToPlayPlugin                                     "Tap here to activate plugin.">
 <!ENTITY clickToActivatePlugin                               "Activate plugin.">
 <!ENTITY checkForUpdates                                     "Check for updates…">
 <!ENTITY blockedPlugin.label                                 "This plugin has been blocked for your protection.">
 <!ENTITY hidePluginBtn.label                                 "Hide plugin">
 <!ENTITY managePlugins                                       "Manage plugins…">
 
 <!-- LOCALIZATION NOTE (reloadPlugin.pre): include a trailing space as needed -->
--- a/toolkit/pluginproblem/content/pluginProblem.xml
+++ b/toolkit/pluginproblem/content/pluginProblem.xml
@@ -22,16 +22,17 @@
     </resources>
 
     <content>
         <html:div class="mainBox" anonid="main" chromedir="&locale.dir;">
             <html:div class="hoverBox">
                 <html:label>
                     <html:button class="icon" anonid="icon"/>
                     <html:div class="msg msgVulnerabilityStatus" anonid="vulnerabilityStatus"><!-- set at runtime --></html:div>
+                    <html:div class="msg msgActivationWarning">&pluginActivationWarning;</html:div>
                     <html:div class="msg msgTapToPlay">&tapToPlayPlugin;</html:div>
                     <html:div class="msg msgClickToPlay" anonid="clickToPlay">&clickToActivatePlugin;</html:div>
                 </html:label>
 
                 <html:div class="msg msgBlocked">&blockedPlugin.label;</html:div>
                 <html:div class="msg msgCrashed">
                     <html:div class="msgCrashedText" anonid="crashedText"><!-- set at runtime --></html:div>
                     <!-- link href set at runtime -->
--- a/toolkit/pluginproblem/content/pluginProblemContent.css
+++ b/toolkit/pluginproblem/content/pluginProblemContent.css
@@ -81,24 +81,23 @@ a .hoverBox,
   cursor: inherit;
 }
 
 .msg {
   display: none;
 }
 
 a .msgClickToPlay,
-a .msgTapToPlay,
 :-moz-handler-clicktoplay .msgClickToPlay,
+:-moz-handler-clicktoplay .msgActivationWarning,
 :-moz-handler-vulnerable-updatable .msgVulnerabilityStatus,
 :-moz-handler-vulnerable-updatable .msgCheckForUpdates,
 :-moz-handler-vulnerable-updatable .msgClickToPlay,
 :-moz-handler-vulnerable-no-update .msgVulnerabilityStatus,
 :-moz-handler-vulnerable-no-update .msgClickToPlay,
-:-moz-handler-clicktoplay .msgTapToPlay,
 :-moz-handler-blocked .msgBlocked,
 :-moz-handler-crashed .msgCrashed {
   display: block;
 }
 
 .submitStatus[status] {
   display: -moz-box;
   -moz-box-align: center;
--- a/toolkit/themes/shared/plugins/pluginProblem.css
+++ b/toolkit/themes/shared/plugins/pluginProblem.css
@@ -27,21 +27,18 @@
   height: 100%;
 }
 :-moz-handler-vulnerable-updatable .hoverBox:active,
 :-moz-handler-vulnerable-no-update .hoverBox:active,
 :-moz-handler-clicktoplay .hoverBox:active {
   background-color: rgb(65, 65, 65);
 }
 
-:-moz-handler-clicktoplay .hoverBox:active .msgTapToPlay,
 :-moz-handler-clicktoplay .hoverBox:active .msgClickToPlay,
-:-moz-handler-vulnerable-updatable .hoverBox:active .msgTapToPlay,
 :-moz-handler-vulnerable-updatable .hoverBox:active .msgClickToPlay,
-:-moz-handler-vulnerable-no-update .hoverBox:active .msgTapToPlay,
 :-moz-handler-vulnerable-no-update .hoverBox:active .msgClickToPlay {
   color: red;
 }
 
 :-moz-handler-vulnerable-updatable .hoverBox,
 :-moz-handler-vulnerable-no-update .hoverBox,
 :-moz-handler-blocked .hoverBox,
 :-moz-handler-crashed .hoverBox {
@@ -88,33 +85,24 @@ a .icon,
 
 @media (min-resolution: 1.1dppx) {
   .throbber {
     background-image: url(chrome://global/skin/icons/loading@2x.png);
     background-size: 16px;
   }
 }
 
-.msgTapToPlay,
 .msgClickToPlay {
   text-decoration: underline;
 }
 
-@media not all and (-moz-touch-enabled) {
-  :-moz-handler-clicktoplay .msgTapToPlay,
-  a .msgTapToPlay  {
-    display: none;
-  }
-}
-
-@media (-moz-touch-enabled) {
-    :-moz-handler-clicktoplay .msgClickToPlay,
-    a .msgClickToPlay {
-    display: none;
-  }
+/* on desktop, don't ever show the tap-to-play UI: that is for mobile only */
+:-moz-handler-clicktoplay .msgTapToPlay,
+a .msgTapToPlay  {
+  display: none;
 }
 
 .submitStatus div {
   min-height: 19px; /* height of biggest line (with throbber) */
 }
 
 .submitComment {
   width: 340px;
--- a/widget/LookAndFeel.h
+++ b/widget/LookAndFeel.h
@@ -463,16 +463,17 @@ public:
     // should be added to the calculated caret width.
     eFloatID_CaretAspectRatio
   };
 
   // These constants must be kept in 1:1 correspondence with the
   // NS_STYLE_FONT_* system font constants.
   enum FontID {
     eFont_Caption = 1,     // css2
+    FontID_MINIMUM = eFont_Caption,
     eFont_Icon,
     eFont_Menu,
     eFont_MessageBox,
     eFont_SmallCaption,
     eFont_StatusBar,
 
     eFont_Window,          // css3
     eFont_Document,
@@ -481,17 +482,18 @@ public:
     eFont_Info,
     eFont_Dialog,
     eFont_Button,
     eFont_PullDownMenu,
     eFont_List,
     eFont_Field,
 
     eFont_Tooltips,        // moz
-    eFont_Widget
+    eFont_Widget,
+    FontID_MAXIMUM = eFont_Widget
   };
 
   /**
    * GetColor() return a native color value (might be overwritten by prefs) for
    * aID.  Some platforms don't return an error even if the index doesn't
    * match any system colors.  And also some platforms may initialize the
    * return value even when it returns an error.  Therefore, if you want to
    * use a color for the default value, you should use the other GetColor()
--- a/widget/windows/nsLookAndFeel.cpp
+++ b/widget/windows/nsLookAndFeel.cpp
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsLookAndFeel.h"
 #include <windows.h>
 #include <shellapi.h>
 #include "nsStyleConsts.h"
 #include "nsUXThemeData.h"
 #include "nsUXThemeConstants.h"
-#include "gfxFont.h"
 #include "WinUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/WindowsVersion.h"
 #include "gfxFontConstants.h"
 
 using namespace mozilla;
 using namespace mozilla::widget;
 
@@ -658,24 +657,53 @@ GetSysFontInfo(HDC aHDC, LookAndFeel::Fo
   return true;
 }
 
 bool
 nsLookAndFeel::GetFontImpl(FontID anID, nsString &aFontName,
                            gfxFontStyle &aFontStyle,
                            float aDevPixPerCSSPixel)
 {
-  HDC tdc = GetDC(nullptr);
-  bool status = GetSysFontInfo(tdc, anID, aFontName, aFontStyle);
-  ReleaseDC(nullptr, tdc);
+  CachedSystemFont &cacheSlot = mSystemFontCache[anID];
+
+  bool status;
+  if (cacheSlot.mCacheValid) {
+    status = cacheSlot.mHaveFont;
+    if (status) {
+      aFontName = cacheSlot.mFontName;
+      aFontStyle = cacheSlot.mFontStyle;
+    }
+  } else {
+    HDC tdc = GetDC(nullptr);
+    status = GetSysFontInfo(tdc, anID, aFontName, aFontStyle);
+    ReleaseDC(nullptr, tdc);
+
+    cacheSlot.mCacheValid = true;
+    cacheSlot.mHaveFont = status;
+    if (status) {
+      cacheSlot.mFontName = aFontName;
+      cacheSlot.mFontStyle = aFontStyle;
+    }
+  }
   // now convert the logical font size from GetSysFontInfo into device pixels for layout
   aFontStyle.size *= aDevPixPerCSSPixel;
   return status;
 }
 
+/* virtual */ void
+nsLookAndFeel::RefreshImpl()
+{
+  nsXPLookAndFeel::RefreshImpl();
+
+  for (auto e = mSystemFontCache.begin(), end = mSystemFontCache.end();
+       e != end; ++e) {
+    e->mCacheValid = false;
+  }
+}
+
 /* virtual */
 char16_t
 nsLookAndFeel::GetPasswordCharacterImpl()
 {
 #define UNICODE_BLACK_CIRCLE_CHAR 0x25cf
   return UNICODE_BLACK_CIRCLE_CHAR;
 }
 
--- a/widget/windows/nsLookAndFeel.h
+++ b/widget/windows/nsLookAndFeel.h
@@ -2,16 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __nsLookAndFeel
 #define __nsLookAndFeel
 
 #include "nsXPLookAndFeel.h"
+#include "gfxFont.h"
+#include "mozilla/RangedArray.h"
 
 /*
  * Gesture System Metrics
  */
 #ifndef SM_DIGITIZER
 #define SM_DIGITIZER         94
 #define TABLET_CONFIG_NONE   0x00000000
 #define NID_INTEGRATED_TOUCH 0x00000001
@@ -31,28 +33,44 @@
 #endif
 
 class nsLookAndFeel: public nsXPLookAndFeel {
   static OperatingSystemVersion GetOperatingSystemVersion();
 public:
   nsLookAndFeel();
   virtual ~nsLookAndFeel();
 
-  virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
-  virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
-  virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
-  virtual bool GetFontImpl(FontID aID, nsString& aFontName,
-                           gfxFontStyle& aFontStyle,
-                           float aDevPixPerCSSPixel);
-  virtual char16_t GetPasswordCharacterImpl();
+  nsresult NativeGetColor(ColorID aID, nscolor &aResult) override;
+  nsresult GetIntImpl(IntID aID, int32_t &aResult) override;
+  nsresult GetFloatImpl(FloatID aID, float &aResult) override;
+  bool GetFontImpl(FontID aID, nsString& aFontName,
+                   gfxFontStyle& aFontStyle,
+                   float aDevPixPerCSSPixel) override;
+  void RefreshImpl() override;
+  char16_t GetPasswordCharacterImpl() override;
 
-  virtual nsTArray<LookAndFeelInt> GetIntCacheImpl();
-  virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache);
+  nsTArray<LookAndFeelInt> GetIntCacheImpl() override;
+  void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache) override;
 
 private:
   // Content process cached values that get shipped over from the browser
   // process.
   int32_t mUseAccessibilityTheme;
   int32_t mUseDefaultTheme; // is the current theme a known default?
   int32_t mNativeThemeId; // see LookAndFeel enum 'WindowsTheme'
+
+  struct CachedSystemFont {
+    CachedSystemFont()
+      : mCacheValid(false)
+    {}
+
+    bool mCacheValid;
+    bool mHaveFont;
+    nsString mFontName;
+    gfxFontStyle mFontStyle;
+  };
+
+  mozilla::RangedArray<CachedSystemFont,
+                       FontID_MINIMUM,
+                       FontID_MAXIMUM + 1 - FontID_MINIMUM> mSystemFontCache;
 };
 
 #endif