merge mozilla-central to mozilla-inbound. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Tue, 12 Sep 2017 11:37:58 +0200
changeset 429768 485c8d248bfc4acc97f92cdc130210d7388bca32
parent 429767 ff02238411b7f263bb085011f586ee111b4e47a9 (current diff)
parent 429740 b0e945eed81db8bf076daf64e381c514f70144f0 (diff)
child 429769 6187a252c146962dbc6554c0406152fefef96313
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-central to mozilla-inbound. r=merge a=merge
layout/reftests/svg/filters/filter-in-mask.svg
memory/mozjemalloc/Makefile.in
memory/mozjemalloc/linkedlist.h
memory/mozjemalloc/moz.build
memory/mozjemalloc/mozjemalloc.cpp
memory/mozjemalloc/mozjemalloc.h
memory/mozjemalloc/mozjemalloc_types.h
memory/mozjemalloc/rb.h
servo/components/style/stylesheets/memory.rs
taskcluster/ci/beetmover-partials/kind.yml
taskcluster/ci/partials-signing/kind.yml
taskcluster/ci/partials/kind.yml
taskcluster/docker/partial-update-generator/Dockerfile
taskcluster/docker/partial-update-generator/Makefile
taskcluster/docker/partial-update-generator/README
taskcluster/docker/partial-update-generator/dep.pubkey
taskcluster/docker/partial-update-generator/nightly_sha1.pubkey
taskcluster/docker/partial-update-generator/nightly_sha384.pubkey
taskcluster/docker/partial-update-generator/release_sha1.pubkey
taskcluster/docker/partial-update-generator/release_sha384.pubkey
taskcluster/docker/partial-update-generator/requirements.txt
taskcluster/docker/partial-update-generator/runme.sh
taskcluster/docker/partial-update-generator/scripts/funsize.py
taskcluster/docker/partial-update-generator/scripts/mbsdiff_hook.sh
taskcluster/taskgraph/transforms/beetmover_partials.py
taskcluster/taskgraph/transforms/partials.py
taskcluster/taskgraph/transforms/partials_signing.py
taskcluster/taskgraph/util/partials.py
toolkit/components/extensions/test/mochitest/test_ext_proxy.html
--- a/accessible/ipc/win/handler/moz.build
+++ b/accessible/ipc/win/handler/moz.build
@@ -62,10 +62,10 @@ elif CONFIG['MOZ_UPDATE_CHANNEL'] == 'be
 
 # This DLL may be loaded into other processes, so we need static libs for
 # Windows 7 and Windows 8.
 USE_STATIC_LIBS = True
 
 LIBRARY_DEFINES['UNICODE'] = True
 LIBRARY_DEFINES['_UNICODE'] = True
 LIBRARY_DEFINES['MOZ_NO_MOZALLOC'] = True
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
--- a/addon-sdk/source/lib/sdk/console/plain-text.js
+++ b/addon-sdk/source/lib/sdk/console/plain-text.js
@@ -57,22 +57,12 @@ function PlainTextConsole(print, innerID
 
   // As we freeze the console object, we can't modify this property afterward
   Object.defineProperty(console, "maxLogLevel", {
     get: function() {
       return logLevel;
     }
   });
 
-  // We defined the `__exposedProps__` in our console chrome object.
-  //
-  // Meanwhile we're investigating with the platform team if `__exposedProps__`
-  // are needed, or are just a left-over.
-
-  console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) {
-    exposed[prop] = "r";
-    return exposed;
-  }, {});
-
   Object.freeze(console);
   return console;
 };
 exports.PlainTextConsole = PlainTextConsole;
--- a/addon-sdk/source/lib/sdk/test/loader.js
+++ b/addon-sdk/source/lib/sdk/test/loader.js
@@ -48,21 +48,16 @@ function HookedPlainTextConsole(hook, pr
   this.log = hook.bind(null, "log", innerID);
   this.info = hook.bind(null, "info", innerID);
   this.warn = hook.bind(null, "warn", innerID);
   this.error = hook.bind(null, "error", innerID);
   this.debug = hook.bind(null, "debug", innerID);
   this.exception = hook.bind(null, "exception", innerID);
   this.time = hook.bind(null, "time", innerID);
   this.timeEnd = hook.bind(null, "timeEnd", innerID);
-
-  this.__exposedProps__ = {
-    log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
-    exception: "rw", time: "rw", timeEnd: "rw"
-  };
 }
 
 // Creates a custom loader instance whose console module is hooked in order
 // to avoid printing messages to the console, and instead, expose them in the
 // returned `messages` array attribute
 exports.LoaderWithHookedConsole = function (module, callback) {
   let messages = [];
   function hook(type, innerID, msg) {
--- a/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
+++ b/addon-sdk/source/test/addons/e10s-content/lib/test-content-script.js
@@ -439,17 +439,17 @@ exports["test Highlight toString Behavio
   function f() {};
   let funToString = Object.prototype.toString.call(f);
   assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");
 
   // This is how jquery call toString:
   let strToString = helper.rawWindow.Object.prototype.toString.call("");
   assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
 
-  let o = {__exposedProps__:{}};
+  let o = {};
   let objToString = helper.rawWindow.Object.prototype.toString.call(o);
   assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
 
   // Make sure to pass a function from the same compartments
   // or toString will return [object Object] on FF8+
   let f2 = helper.rawWindow.eval("(function () {})");
   let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);
   assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");
@@ -617,20 +617,16 @@ exports["test Prototype Inheritance"] = 
 exports["test Functions"] = createProxyTest("", function (helper) {
 
   helper.rawWindow.callFunction = function callFunction(f) {
     return f();
   };
   helper.rawWindow.isEqual = function isEqual(a, b) {
     return a == b;
   };
-  // bug 784116: workaround in order to allow proxy code to cache proxies on
-  // these functions:
-  helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
-  helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
 
   helper.createWorker(
     'new ' + function ContentScriptScope() {
       // Check basic usage of functions
       let closure2 = function () {return "ok";};
       assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
 
       // Ensure that functions are cached when being wrapped to native code
--- a/addon-sdk/source/test/test-content-script.js
+++ b/addon-sdk/source/test/test-content-script.js
@@ -439,17 +439,17 @@ exports["test Highlight toString Behavio
   function f() {};
   let funToString = Object.prototype.toString.call(f);
   assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");
 
   // This is how jquery call toString:
   let strToString = helper.rawWindow.Object.prototype.toString.call("");
   assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
 
-  let o = {__exposedProps__:{}};
+  let o = {};
   let objToString = helper.rawWindow.Object.prototype.toString.call(o);
   assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
 
   // Make sure to pass a function from the same compartments
   // or toString will return [object Object] on FF8+
   let f2 = helper.rawWindow.eval("(function () {})");
   let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);
   assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");
@@ -617,20 +617,16 @@ exports["test Prototype Inheritance"] = 
 exports["test Functions"] = createProxyTest("", function (helper) {
 
   helper.rawWindow.callFunction = function callFunction(f) {
     return f();
   };
   helper.rawWindow.isEqual = function isEqual(a, b) {
     return a == b;
   };
-  // bug 784116: workaround in order to allow proxy code to cache proxies on
-  // these functions:
-  helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
-  helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
 
   helper.createWorker(
     'new ' + function ContentScriptScope() {
       // Check basic usage of functions
       let closure2 = function () {return "ok";};
       assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
 
       // Ensure that functions are cached when being wrapped to native code
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -96,17 +96,17 @@ if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_
 #
 # The default heap size is 1MB on Win32.
 # The heap will grow if need be.
 #
 # Set it to 256k.  See bug 127069.
 if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
     LDFLAGS += ['/HEAP:0x40000']
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
 
 if CONFIG['MOZ_GPSD']:
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5174,17 +5174,19 @@
           return switcher;
         ]]></body>
       </method>
 
       <method name="warmupTab">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
-            this._getSwitcher().warmupTab(aTab);
+            if (gMultiProcessBrowser) {
+              this._getSwitcher().warmupTab(aTab);
+            }
           ]]>
         </body>
       </method>
 
       <!-- BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
            MAKE SURE TO ADD IT HERE AS WELL. -->
       <property name="canGoBack"
                 onget="return this.mCurrentBrowser.canGoBack;"
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -82,16 +82,20 @@ var whitelist = [
 
   // Add-on API introduced in bug 1118285
   {file: "resource://app/modules/NewTabURL.jsm"},
 
   // browser/components/newtab bug 1355166
   {file: "resource://app/modules/NewTabSearchProvider.jsm"},
   {file: "resource://app/modules/NewTabWebChannel.jsm"},
 
+  // browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
+  // This will used when Bug 1397875 lands
+  {file: "resource://activity-stream/data/content/activity-stream-prerendered.html"},
+
   // layout/mathml/nsMathMLChar.cpp
   {file: "resource://gre/res/fonts/mathfontSTIXGeneral.properties"},
   {file: "resource://gre/res/fonts/mathfontUnicode.properties"},
 
   // toolkit/components/places/ColorAnalyzer_worker.js
   {file: "resource://gre/modules/ClusterLib.js"},
   {file: "resource://gre/modules/ColorConversion.js"},
 
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2689,27 +2689,25 @@ file, You can obtain one at http://mozil
                     "center-item-menulist").value = val;
         </setter>
       </property>
     </implementation>
   </binding>
 
   <binding id="click-to-play-plugins-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
     <content align="start" style="width: &pluginNotification.width;;">
-      <xul:vbox flex="1" align="stretch" class="popup-notification-main-box"
+      <xul:vbox flex="1" align="stretch" class="click-to-play-plugins-notification-main-box"
                 xbl:inherits="popupid">
         <xul:hbox class="click-to-play-plugins-notification-description-box" flex="1" align="start">
           <xul:description class="click-to-play-plugins-outer-description" flex="1">
             <html:span anonid="click-to-play-plugins-notification-description" />
-            <xul:label class="text-link click-to-play-plugins-notification-link" anonid="click-to-play-plugins-notification-link" />
+            <html:br/>
+            <xul:label class="text-link click-to-play-plugins-notification-link popup-notification-learnmore-link"
+                       anonid="click-to-play-plugins-notification-link" />
           </xul:description>
-          <xul:toolbarbutton anonid="closebutton"
-                             class="messageCloseButton popup-notification-closebutton tabbable close-icon"
-                             xbl:inherits="oncommand=closebuttoncommand"
-                             tooltiptext="&closeNotification.tooltip;"/>
         </xul:hbox>
         <xul:grid anonid="click-to-play-plugins-notification-center-box"
                   class="click-to-play-plugins-notification-center-box">
           <xul:columns>
             <xul:column flex="1"/>
             <xul:column/>
           </xul:columns>
           <xul:rows>
@@ -2721,21 +2719,22 @@ file, You can obtain one at http://mozil
                           oncommand="document.getBindingParent(this)._setState(2)"/>
             </xul:hbox>
           </xul:rows>
         </xul:grid>
         <xul:hbox anonid="button-container"
                   class="click-to-play-plugins-notification-button-container"
                   pack="center" align="center">
           <xul:button anonid="primarybutton"
-                      class="click-to-play-popup-button"
+                      class="click-to-play-popup-button popup-notification-button"
                       oncommand="document.getBindingParent(this)._onButton(this)"
                       flex="1"/>
           <xul:button anonid="secondarybutton"
-                      class="click-to-play-popup-button"
+                      default="true"
+                      class="click-to-play-popup-button popup-notification-button"
                       oncommand="document.getBindingParent(this)._onButton(this);"
                       flex="1"/>
         </xul:hbox>
         <xul:box hidden="true">
           <children/>
         </xul:box>
       </xul:vbox>
     </content>
--- a/browser/components/extensions/schemas/devtools.json
+++ b/browser/components/extensions/schemas/devtools.json
@@ -5,17 +5,27 @@
       {
         "$extend": "WebExtensionManifest",
         "properties": {
           "devtools_page": {
             "$ref": "ExtensionURL",
             "optional": true
           }
         }
+      },
+      {
+        "$extend": "Permission",
+        "choices": [{
+          "type": "string",
+          "enum": [
+            "devtools"
+          ]
+        }]
       }
     ]
   },
   {
     "namespace": "devtools",
+    "permissions": ["devtools"],
     "allowedContexts": ["devtools", "devtools_only"],
     "defaultContexts": ["devtools", "devtools_only"]
   }
 ]
--- a/browser/components/search/test/browser_google_behavior.js
+++ b/browser/components/search/test/browser_google_behavior.js
@@ -51,16 +51,23 @@ function promiseStateChangeURI() {
 
     gBrowser.addProgressListener(listener);
   });
 }
 
 function promiseContentSearchReady(browser) {
   return ContentTask.spawn(browser, {}, async function(args) {
     return new Promise(resolve => {
+      if (content.wrappedJSObject.gContentSearchController) {
+        let searchController = content.wrappedJSObject.gContentSearchController;
+        if (searchController.defaultEngine) {
+          resolve();
+        }
+      }
+
       content.addEventListener("ContentSearchService", function listener(aEvent) {
         if (aEvent.detail.type == "State") {
           content.removeEventListener("ContentSearchService", listener);
           resolve();
         }
       });
     });
   });
--- a/browser/components/search/test/browser_searchEngine_behaviors.js
+++ b/browser/components/search/test/browser_searchEngine_behaviors.js
@@ -90,24 +90,19 @@ function promiseStateChangeURI() {
     }
 
     gBrowser.addProgressListener(listener);
   });
 }
 
 function promiseContentSearchReady(browser) {
   return ContentTask.spawn(browser, {}, async function(args) {
-    return new Promise(resolve => {
-      content.addEventListener("ContentSearchService", function listener(aEvent) {
-        if (aEvent.detail.type == "State") {
-          content.removeEventListener("ContentSearchService", listener);
-          resolve();
-        }
-      });
-    });
+    await ContentTaskUtils.waitForCondition(() => content.wrappedJSObject.gContentSearchController &&
+      content.wrappedJSObject.gContentSearchController.defaultEngine
+    );
   });
 }
 
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({ set: [
     ["browser.search.widget.inNavBar", true],
   ]});
 });
--- a/browser/extensions/activity-stream/common/Actions.jsm
+++ b/browser/extensions/activity-stream/common/Actions.jsm
@@ -34,16 +34,17 @@ for (const type of [
   "INIT",
   "LOCALE_UPDATED",
   "MIGRATION_CANCEL",
   "MIGRATION_COMPLETED",
   "MIGRATION_START",
   "NEW_TAB_INIT",
   "NEW_TAB_INITIAL_STATE",
   "NEW_TAB_LOAD",
+  "NEW_TAB_STATE_REQUEST",
   "NEW_TAB_UNLOAD",
   "OPEN_LINK",
   "OPEN_NEW_WINDOW",
   "OPEN_PRIVATE_WINDOW",
   "PINNED_SITES_UPDATED",
   "PLACES_BOOKMARK_ADDED",
   "PLACES_BOOKMARK_CHANGED",
   "PLACES_BOOKMARK_REMOVED",
--- a/browser/extensions/activity-stream/common/PerfService.jsm
+++ b/browser/extensions/activity-stream/common/PerfService.jsm
@@ -1,31 +1,33 @@
 /* globals Services */
 "use strict";
 
+/* istanbul ignore if */
+if (typeof Components !== "undefined" && Components.utils) {
+  Components.utils.import("resource://gre/modules/Services.jsm");
+}
+
 let usablePerfObj;
 
-let Cu;
-const isRunningInChrome = typeof Window === "undefined";
-
 /* istanbul ignore if */
-if (isRunningInChrome) {
-  Cu = Components.utils;
-} else {
-  Cu = {import() {}};
-}
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-/* istanbul ignore if */
-if (isRunningInChrome) {
+/* istanbul ignore else */
+if (typeof Services !== "undefined") {
   // Borrow the high-resolution timer from the hidden window....
   usablePerfObj = Services.appShell.hiddenDOMWindow.performance;
-} else { // we must be running in content space
+} else if (typeof performance !== "undefined") {
+  // we must be running in content space
   usablePerfObj = performance;
+} else {
+  // This is a dummy object so this file doesn't crash in the node prerendering
+  // task.
+  usablePerfObj = {
+    now() {},
+    mark() {}
+  };
 }
 
 this._PerfService = function _PerfService(options) {
   // For testing, so that we can use a fake Window.performance object with
   // known state.
   if (options && options.performanceObj) {
     this._perf = options.performanceObj;
   } else {
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/common/PrerenderData.jsm
@@ -0,0 +1,42 @@
+const prefConfig = {
+  // Prefs listed with "invalidates: true" will prevent the prerendered version
+  // of AS from being used if their value is something other than what is listed
+  // here. This is required because some preferences cause the page layout to be
+  // too different for the prerendered version to be used. Unfortunately, this
+  // will result in users who have modified some of their preferences not being
+  // able to get the benefits of prerendering.
+  "migrationExpired": {value: true},
+  "showTopSites": {
+    value: true,
+    invalidates: true
+  },
+  "showSearch": {
+    value: true,
+    invalidates: true
+  },
+  "topSitesCount": {value: 6},
+  "feeds.section.topstories": {
+    value: true,
+    invalidates: true
+  }
+};
+
+this.PrerenderData = {
+  invalidatingPrefs: Object.keys(prefConfig).filter(key => prefConfig[key].invalidates),
+  initialPrefs: Object.keys(prefConfig).reduce((obj, key) => {
+    obj[key] = prefConfig[key].value;
+    return obj;
+  }, {}), // This creates an object of the form {prefName: value}
+  initialSections: [
+    {
+      enabled: true,
+      icon: "pocket",
+      id: "topstories",
+      order: 1,
+      title: {id: "header_recommended_by", values: {provider: "Pocket"}},
+      topics: [{}]
+    }
+  ]
+};
+
+this.EXPORTED_SYMBOLS = ["PrerenderData"];
--- a/browser/extensions/activity-stream/common/Reducers.jsm
+++ b/browser/extensions/activity-stream/common/Reducers.jsm
@@ -1,26 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {actionTypes: at} = Components.utils.import("resource://activity-stream/common/Actions.jsm", {});
 
+// Locales that should be displayed RTL
+const RTL_LIST = ["ar", "he", "fa", "ur"];
+
 const TOP_SITES_DEFAULT_LENGTH = 6;
 const TOP_SITES_SHOWMORE_LENGTH = 12;
 
 const INITIAL_STATE = {
   App: {
     // Have we received real data from the app yet?
     initialized: false,
     // The locale of the browser
     locale: "",
     // Localized strings with defaults
     strings: null,
+    // The text direction for the locale
+    textDirection: "",
     // The version of the system-addon
     version: null
   },
   Snippets: {initialized: false},
   TopSites: {
     // Have we received real data from history yet?
     initialized: false,
     // The history (and possibly default) links
@@ -43,17 +48,18 @@ function App(prevState = INITIAL_STATE.A
       return Object.assign({}, prevState, action.data || {}, {initialized: true});
     case at.LOCALE_UPDATED: {
       if (!action.data) {
         return prevState;
       }
       let {locale, strings} = action.data;
       return Object.assign({}, prevState, {
         locale,
-        strings
+        strings,
+        textDirection: RTL_LIST.indexOf(locale.split("-")[0]) >= 0 ? "rtl" : "ltr"
       });
     }
     default:
       return prevState;
   }
 }
 
 /**
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream-initial-state.js
@@ -0,0 +1,142 @@
+// Note - this is a generated file.
+  window.gActivityStreamPrerenderedState = {
+  "TopSites": {
+    "initialized": false,
+    "rows": []
+  },
+  "App": {
+    "initialized": false,
+    "locale": "en-PRERENDER",
+    "strings": {
+      "newtab_page_title": " ",
+      "default_label_loading": " ",
+      "header_top_sites": " ",
+      "header_stories": " ",
+      "header_highlights": " ",
+      "header_visit_again": " ",
+      "header_bookmarks": " ",
+      "header_recommended_by": " ",
+      "header_bookmarks_placeholder": " ",
+      "header_stories_from": " ",
+      "type_label_visited": " ",
+      "type_label_bookmarked": " ",
+      "type_label_synced": " ",
+      "type_label_recommended": " ",
+      "type_label_open": " ",
+      "type_label_topic": " ",
+      "type_label_now": " ",
+      "menu_action_bookmark": " ",
+      "menu_action_remove_bookmark": " ",
+      "menu_action_copy_address": " ",
+      "menu_action_email_link": " ",
+      "menu_action_open_new_window": " ",
+      "menu_action_open_private_window": " ",
+      "menu_action_dismiss": " ",
+      "menu_action_delete": " ",
+      "menu_action_pin": " ",
+      "menu_action_unpin": " ",
+      "confirm_history_delete_p1": " ",
+      "confirm_history_delete_notice_p2": " ",
+      "menu_action_save_to_pocket": " ",
+      "search_for_something_with": " ",
+      "search_button": " ",
+      "search_header": " ",
+      "search_web_placeholder": "Search the Web",
+      "search_settings": " ",
+      "section_info_option": " ",
+      "section_info_send_feedback": " ",
+      "section_info_privacy_notice": " ",
+      "welcome_title": " ",
+      "welcome_body": " ",
+      "welcome_label": " ",
+      "time_label_less_than_minute": " ",
+      "time_label_minute": " ",
+      "time_label_hour": " ",
+      "time_label_day": " ",
+      "settings_pane_button_label": " ",
+      "settings_pane_header": " ",
+      "settings_pane_body2": " ",
+      "settings_pane_search_header": " ",
+      "settings_pane_search_body": " ",
+      "settings_pane_topsites_header": " ",
+      "settings_pane_topsites_body": " ",
+      "settings_pane_topsites_options_showmore": " ",
+      "settings_pane_bookmarks_header": " ",
+      "settings_pane_bookmarks_body": " ",
+      "settings_pane_visit_again_header": " ",
+      "settings_pane_visit_again_body": " ",
+      "settings_pane_highlights_header": " ",
+      "settings_pane_highlights_body2": " ",
+      "settings_pane_highlights_options_bookmarks": " ",
+      "settings_pane_highlights_options_visited": " ",
+      "settings_pane_snippets_header": " ",
+      "settings_pane_snippets_body": " ",
+      "settings_pane_done_button": " ",
+      "edit_topsites_button_text": " ",
+      "edit_topsites_button_label": " ",
+      "edit_topsites_showmore_button": " ",
+      "edit_topsites_showless_button": " ",
+      "edit_topsites_done_button": " ",
+      "edit_topsites_pin_button": " ",
+      "edit_topsites_unpin_button": " ",
+      "edit_topsites_edit_button": " ",
+      "edit_topsites_dismiss_button": " ",
+      "edit_topsites_add_button": " ",
+      "topsites_form_add_header": " ",
+      "topsites_form_edit_header": " ",
+      "topsites_form_title_placeholder": " ",
+      "topsites_form_url_placeholder": " ",
+      "topsites_form_add_button": " ",
+      "topsites_form_save_button": " ",
+      "topsites_form_cancel_button": " ",
+      "topsites_form_url_validation": " ",
+      "pocket_read_more": " ",
+      "pocket_read_even_more": " ",
+      "pocket_feedback_header": " ",
+      "pocket_description": " ",
+      "highlights_empty_state": " ",
+      "topstories_empty_state": " ",
+      "manual_migration_explanation2": " ",
+      "manual_migration_cancel_button": " ",
+      "manual_migration_import_button": " "
+    },
+    "textDirection": "ltr",
+    "version": null
+  },
+  "Snippets": {
+    "initialized": false
+  },
+  "Prefs": {
+    "initialized": true,
+    "values": {
+      "migrationExpired": true,
+      "showTopSites": true,
+      "showSearch": true,
+      "topSitesCount": 6,
+      "feeds.section.topstories": true
+    }
+  },
+  "Dialog": {
+    "visible": false,
+    "data": {}
+  },
+  "Sections": [
+    {
+      "title": {
+        "id": "header_recommended_by",
+        "values": {
+          "provider": "Pocket"
+        }
+      },
+      "rows": [],
+      "order": 1,
+      "enabled": true,
+      "icon": "pocket",
+      "id": "topstories",
+      "topics": [
+        {}
+      ],
+      "initialized": false
+    }
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/data/content/activity-stream-prerendered.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html lang="" dir="ltr">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp">
+    <title></title>
+    <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
+    <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
+    <link rel="stylesheet" href="resource://activity-stream/data/content/activity-stream.css" />
+  </head>
+  <body class="activity-stream">
+    <div id="root"><div class="outer-wrapper" data-reactroot="" data-reactid="1" data-react-checksum="57168132"><main data-reactid="2"><div class="search-wrapper" data-reactid="3"><label for="newtab-search-text" class="search-label" data-reactid="4"><span class="sr-only" data-reactid="5"><span data-reactid="6">Search the Web</span></span></label><input type="search" id="newtab-search-text" maxlength="256" placeholder="Search the Web" title="Search the Web" data-reactid="7"/><button id="searchSubmit" class="search-button" title=" " data-reactid="8"><span class="sr-only" data-reactid="9"><span data-reactid="10"> </span></span></button></div><section class="top-sites" data-reactid="11"><h3 class="section-title" data-reactid="12"><span class="icon icon-small-spacer icon-topsites" data-reactid="13"></span><span data-reactid="14"> </span></h3><ul class="top-sites-list" data-reactid="15"><li class="top-site-outer placeholder" data-reactid="16"><a data-reactid="17"><div class="tile" aria-hidden="true" data-reactid="18"><span class="letter-fallback" data-reactid="19"></span><div class="screenshot" style="background-image:none;" data-reactid="20"></div></div><div class="title " data-reactid="21"><span dir="auto" data-reactid="22"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="23"><a data-reactid="24"><div class="tile" aria-hidden="true" data-reactid="25"><span class="letter-fallback" data-reactid="26"></span><div class="screenshot" style="background-image:none;" data-reactid="27"></div></div><div class="title " data-reactid="28"><span dir="auto" data-reactid="29"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="30"><a data-reactid="31"><div class="tile" aria-hidden="true" data-reactid="32"><span class="letter-fallback" data-reactid="33"></span><div class="screenshot" style="background-image:none;" data-reactid="34"></div></div><div class="title " data-reactid="35"><span dir="auto" data-reactid="36"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="37"><a data-reactid="38"><div class="tile" aria-hidden="true" data-reactid="39"><span class="letter-fallback" data-reactid="40"></span><div class="screenshot" style="background-image:none;" data-reactid="41"></div></div><div class="title " data-reactid="42"><span dir="auto" data-reactid="43"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="44"><a data-reactid="45"><div class="tile" aria-hidden="true" data-reactid="46"><span class="letter-fallback" data-reactid="47"></span><div class="screenshot" style="background-image:none;" data-reactid="48"></div></div><div class="title " data-reactid="49"><span dir="auto" data-reactid="50"></span></div></a></li><li class="top-site-outer placeholder" data-reactid="51"><a data-reactid="52"><div class="tile" aria-hidden="true" data-reactid="53"><span class="letter-fallback" data-reactid="54"></span><div class="screenshot" style="background-image:none;" data-reactid="55"></div></div><div class="title " data-reactid="56"><span dir="auto" data-reactid="57"></span></div></a></li></ul></section><div class="sections-list" data-reactid="58"><section data-reactid="59"><div class="section-top-bar" data-reactid="60"><h3 class="section-title" data-reactid="61"><span class="icon icon-small-spacer icon-pocket" data-reactid="62"></span><span data-reactid="63"> </span></h3></div><ul class="section-list" style="padding:0;" data-reactid="64"><li class="card-outer placeholder" data-reactid="65"><a data-reactid="66"><div class="card" data-reactid="67"><div class="card-details no-image" data-reactid="68"><div class="card-text no-image no-host-name no-context" data-reactid="69"><h4 class="card-title" dir="auto" data-reactid="70"></h4><p class="card-description" dir="auto" data-reactid="71"></p></div></div></div></a></li><li class="card-outer placeholder" data-reactid="72"><a data-reactid="73"><div class="card" data-reactid="74"><div class="card-details no-image" data-reactid="75"><div class="card-text no-image no-host-name no-context" data-reactid="76"><h4 class="card-title" dir="auto" data-reactid="77"></h4><p class="card-description" dir="auto" data-reactid="78"></p></div></div></div></a></li><li class="card-outer placeholder" data-reactid="79"><a data-reactid="80"><div class="card" data-reactid="81"><div class="card-details no-image" data-reactid="82"><div class="card-text no-image no-host-name no-context" data-reactid="83"><h4 class="card-title" dir="auto" data-reactid="84"></h4><p class="card-description" dir="auto" data-reactid="85"></p></div></div></div></a></li></ul><div class="topic" data-reactid="86"><span data-reactid="87"><span data-reactid="88"> </span></span><ul data-reactid="89"><li data-reactid="90"><a class="topic-link" data-reactid="91"></a></li></ul></div></section></div><!-- react-empty: 92 --></main></div></div>
+    <div id="snippets-container">
+      <div id="snippets"></div>
+    </div>
+<script src="resource://activity-stream/data/content/activity-stream-initial-state.js"></script>
+    <script src="chrome://browser/content/contentSearchUI.js"></script>
+    <script src="resource://activity-stream/vendor/react.js"></script>
+    <script src="resource://activity-stream/vendor/react-dom.js"></script>
+    <script src="resource://activity-stream/vendor/react-intl.js"></script>
+    <script src="resource://activity-stream/vendor/redux.js"></script>
+    <script src="resource://activity-stream/vendor/react-redux.js"></script>
+    <script src="resource://activity-stream/data/content/activity-stream.bundle.js"></script>
+  </body>
+</html>
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -60,22 +60,16 @@
 /******/ 	__webpack_require__.p = "";
 /******/
 /******/ 	// Load entry module and return exports
 /******/ 	return __webpack_require__(__webpack_require__.s = 10);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
-/***/ (function(module, exports) {
-
-module.exports = React;
-
-/***/ }),
-/* 1 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 /* 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/. */
 
 
@@ -95,17 +89,17 @@ const globalImportContext = typeof Windo
 
 
 // Create an object that avoids accidental differing key/value pairs:
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 const actionTypes = {};
-for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PINNED_SITES_UPDATED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
+for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "INIT", "LOCALE_UPDATED", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PINNED_SITES_UPDATED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_REGISTER", "SECTION_UPDATE", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_DATA", "SNIPPETS_RESET", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
   actionTypes[type] = type;
 }
 
 // Helper function for creating routed actions between content and main
 // Not intended to be used by consumers
 function _RouteMessage(action, options) {
   const meta = action.meta ? Object.assign({}, action.meta) : {};
   if (!options || !options.from || !options.to) {
@@ -186,63 +180,55 @@ function UserEvent(data) {
 
 /**
  * UndesiredEvent - A telemetry ping indicating an undesired state.
  *
  * @param  {object} data Fields to include in the ping (value, etc.)
  * @param  {int} importContext (For testing) Override the import context for testing.
  * @return {object} An action. For UI code, a SendToMain action.
  */
-function UndesiredEvent(data) {
-  let importContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalImportContext;
-
+function UndesiredEvent(data, importContext = globalImportContext) {
   const action = {
     type: actionTypes.TELEMETRY_UNDESIRED_EVENT,
     data
   };
   return importContext === UI_CODE ? SendToMain(action) : action;
 }
 
 /**
  * PerfEvent - A telemetry ping indicating a performance-related event.
  *
  * @param  {object} data Fields to include in the ping (value, etc.)
  * @param  {int} importContext (For testing) Override the import context for testing.
  * @return {object} An action. For UI code, a SendToMain action.
  */
-function PerfEvent(data) {
-  let importContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalImportContext;
-
+function PerfEvent(data, importContext = globalImportContext) {
   const action = {
     type: actionTypes.TELEMETRY_PERFORMANCE_EVENT,
     data
   };
   return importContext === UI_CODE ? SendToMain(action) : action;
 }
 
 /**
  * ImpressionStats - A telemetry ping indicating an impression stats.
  *
  * @param  {object} data Fields to include in the ping
  * @param  {int} importContext (For testing) Override the import context for testing.
  * #return {object} An action. For UI code, a SendToMain action.
  */
-function ImpressionStats(data) {
-  let importContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalImportContext;
-
+function ImpressionStats(data, importContext = globalImportContext) {
   const action = {
     type: actionTypes.TELEMETRY_IMPRESSION_STATS,
     data
   };
   return importContext === UI_CODE ? SendToMain(action) : action;
 }
 
-function SetPref(name, value) {
-  let importContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : globalImportContext;
-
+function SetPref(name, value, importContext = globalImportContext) {
   const action = { type: actionTypes.SET_PREF, data: { name, value } };
   return importContext === UI_CODE ? SendToMain(action) : action;
 }
 
 var actionCreators = {
   BroadcastToContent,
   UserEvent,
   UndesiredEvent,
@@ -292,65 +278,101 @@ module.exports = {
   globalImportContext,
   UI_CODE,
   BACKGROUND_PROCESS,
   MAIN_MESSAGE_TYPE,
   CONTENT_MESSAGE_TYPE
 };
 
 /***/ }),
+/* 1 */
+/***/ (function(module, exports) {
+
+module.exports = React;
+
+/***/ }),
 /* 2 */
 /***/ (function(module, exports) {
 
 module.exports = ReactIntl;
 
 /***/ }),
 /* 3 */
 /***/ (function(module, exports) {
 
 module.exports = ReactRedux;
 
 /***/ }),
 /* 4 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, exports) {
 
-"use strict";
+var g;
+
+// This works in non-strict mode
+g = (function() {
+	return this;
+})();
+
+try {
+	// This works if eval is allowed (see CSP)
+	g = g || Function("return this")() || (1,eval)("this");
+} catch(e) {
+	// This works if the window reference is available
+	if(typeof window === "object")
+		g = window;
+}
+
+// g can still be undefined, but nothing to do about it...
+// We return undefined, instead of nothing here, so it's
+// easier to handle this case. if(!global) { ...}
+
+module.exports = g;
 
 
+/***/ }),
+/* 5 */
+/***/ (function(module, exports) {
+
 module.exports = {
   TOP_SITES_SOURCE: "TOP_SITES",
-  TOP_SITES_CONTEXT_MENU_OPTIONS: ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"]
+  TOP_SITES_CONTEXT_MENU_OPTIONS: ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
+  // minimum size necessary to show a rich icon instead of a screenshot
+  MIN_RICH_FAVICON_SIZE: 96,
+  // minimum size necessary to show any icon in the top left corner with a screenshot
+  MIN_CORNER_FAVICON_SIZE: 32
 };
 
 /***/ }),
-/* 5 */
+/* 6 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 /* 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/. */
 
 
-var _require = __webpack_require__(1);
+const { actionTypes: at } = __webpack_require__(0);
 
-const at = _require.actionTypes;
-
+// Locales that should be displayed RTL
+const RTL_LIST = ["ar", "he", "fa", "ur"];
 
 const TOP_SITES_DEFAULT_LENGTH = 6;
 const TOP_SITES_SHOWMORE_LENGTH = 12;
 
 const INITIAL_STATE = {
   App: {
     // Have we received real data from the app yet?
     initialized: false,
     // The locale of the browser
     locale: "",
     // Localized strings with defaults
     strings: null,
+    // The text direction for the locale
+    textDirection: "",
     // The version of the system-addon
     version: null
   },
   Snippets: { initialized: false },
   TopSites: {
     // Have we received real data from history yet?
     initialized: false,
     // The history (and possibly default) links
@@ -362,35 +384,30 @@ const INITIAL_STATE = {
   },
   Dialog: {
     visible: false,
     data: {}
   },
   Sections: []
 };
 
-function App() {
-  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.App;
-  let action = arguments[1];
-
+function App(prevState = INITIAL_STATE.App, action) {
   switch (action.type) {
     case at.INIT:
       return Object.assign({}, prevState, action.data || {}, { initialized: true });
     case at.LOCALE_UPDATED:
       {
         if (!action.data) {
           return prevState;
         }
-        var _action$data = action.data;
-        let locale = _action$data.locale,
-            strings = _action$data.strings;
-
+        let { locale, strings } = action.data;
         return Object.assign({}, prevState, {
           locale,
-          strings
+          strings,
+          textDirection: RTL_LIST.indexOf(locale.split("-")[0]) >= 0 ? "rtl" : "ltr"
         });
       }
     default:
       return prevState;
   }
 }
 
 /**
@@ -423,20 +440,17 @@ function insertPinned(links, pinned) {
     } else {
       newLinks.splice(index, 0, link);
     }
   });
 
   return newLinks;
 }
 
-function TopSites() {
-  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.TopSites;
-  let action = arguments[1];
-
+function TopSites(prevState = INITIAL_STATE.TopSites, action) {
   let hasMatch;
   let newRows;
   let pinned;
   switch (action.type) {
     case at.TOP_SITES_UPDATED:
       if (!action.data) {
         return prevState;
       }
@@ -451,21 +465,17 @@ function TopSites() {
       });
       return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState;
     case at.PLACES_BOOKMARK_ADDED:
       if (!action.data) {
         return prevState;
       }
       newRows = prevState.rows.map(site => {
         if (site && site.url === action.data.url) {
-          var _action$data2 = action.data;
-          const bookmarkGuid = _action$data2.bookmarkGuid,
-                bookmarkTitle = _action$data2.bookmarkTitle,
-                dateAdded = _action$data2.dateAdded;
-
+          const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
           return Object.assign({}, site, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded });
         }
         return site;
       });
       return Object.assign({}, prevState, { rows: newRows });
     case at.PLACES_BOOKMARK_REMOVED:
       if (!action.data) {
         return prevState;
@@ -492,53 +502,44 @@ function TopSites() {
       pinned = action.data;
       newRows = insertPinned(prevState.rows, pinned).slice(0, TOP_SITES_SHOWMORE_LENGTH);
       return Object.assign({}, prevState, { rows: newRows });
     default:
       return prevState;
   }
 }
 
-function Dialog() {
-  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Dialog;
-  let action = arguments[1];
-
+function Dialog(prevState = INITIAL_STATE.Dialog, action) {
   switch (action.type) {
     case at.DIALOG_OPEN:
       return Object.assign({}, prevState, { visible: true, data: action.data });
     case at.DIALOG_CANCEL:
       return Object.assign({}, prevState, { visible: false });
     case at.DELETE_HISTORY_URL:
       return Object.assign({}, INITIAL_STATE.Dialog);
     default:
       return prevState;
   }
 }
 
-function Prefs() {
-  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Prefs;
-  let action = arguments[1];
-
+function Prefs(prevState = INITIAL_STATE.Prefs, action) {
   let newValues;
   switch (action.type) {
     case at.PREFS_INITIAL_VALUES:
       return Object.assign({}, prevState, { initialized: true, values: action.data });
     case at.PREF_CHANGED:
       newValues = Object.assign({}, prevState.values);
       newValues[action.data.name] = action.data.value;
       return Object.assign({}, prevState, { values: newValues });
     default:
       return prevState;
   }
 }
 
-function Sections() {
-  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Sections;
-  let action = arguments[1];
-
+function Sections(prevState = INITIAL_STATE.Sections, action) {
   let hasMatch;
   let newState;
   switch (action.type) {
     case at.SECTION_DEREGISTER:
       return prevState.filter(section => section.id !== action.data);
     case at.SECTION_REGISTER:
       // If section exists in prevState, update it
       newState = prevState.map(section => {
@@ -585,21 +586,17 @@ function Sections() {
     case at.PLACES_BOOKMARK_ADDED:
       if (!action.data) {
         return prevState;
       }
       return prevState.map(section => Object.assign({}, section, {
         rows: section.rows.map(item => {
           // find the item within the rows that is attempted to be bookmarked
           if (item.url === action.data.url) {
-            var _action$data3 = action.data;
-            const bookmarkGuid = _action$data3.bookmarkGuid,
-                  bookmarkTitle = _action$data3.bookmarkTitle,
-                  dateAdded = _action$data3.dateAdded;
-
+            const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
             Object.assign(item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded });
             if (!item.type || item.type === "history") {
               item.type = "bookmark";
             }
           }
           return item;
         })
       }));
@@ -626,20 +623,17 @@ function Sections() {
     case at.PLACES_LINK_DELETED:
     case at.PLACES_LINK_BLOCKED:
       return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => site.url !== action.data.url) }));
     default:
       return prevState;
   }
 }
 
-function Snippets() {
-  let prevState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : INITIAL_STATE.Snippets;
-  let action = arguments[1];
-
+function Snippets(prevState = INITIAL_STATE.Snippets, action) {
   switch (action.type) {
     case at.SNIPPETS_DATA:
       return Object.assign({}, prevState, { initialized: true }, action.data);
     case at.SNIPPETS_RESET:
       return INITIAL_STATE.Snippets;
     default:
       return prevState;
   }
@@ -650,44 +644,46 @@ module.exports = {
   reducers,
   INITIAL_STATE,
   insertPinned,
   TOP_SITES_DEFAULT_LENGTH,
   TOP_SITES_SHOWMORE_LENGTH
 };
 
 /***/ }),
-/* 6 */
+/* 7 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 /* globals Services */
 
 
-let usablePerfObj;
+/* istanbul ignore if */
 
-let Cu;
-const isRunningInChrome = typeof Window === "undefined";
+if (typeof Components !== "undefined" && Components.utils) {
+  Components.utils.import("resource://gre/modules/Services.jsm");
+}
+
+let usablePerfObj;
 
 /* istanbul ignore if */
-if (isRunningInChrome) {
-  Cu = Components.utils;
-} else {
-  Cu = { import() {} };
-}
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-/* istanbul ignore if */
-if (isRunningInChrome) {
+/* istanbul ignore else */
+if (typeof Services !== "undefined") {
   // Borrow the high-resolution timer from the hidden window....
   usablePerfObj = Services.appShell.hiddenDOMWindow.performance;
-} else {
+} else if (typeof performance !== "undefined") {
   // we must be running in content space
   usablePerfObj = performance;
+} else {
+  // This is a dummy object so this file doesn't crash in the node prerendering
+  // task.
+  usablePerfObj = {
+    now() {},
+    mark() {}
+  };
 }
 
 var _PerfService = function _PerfService(options) {
   // For testing, so that we can use a fake Window.performance object with
   // known state.
   if (options && options.performanceObj) {
     this._perf = options.performanceObj;
   } else {
@@ -773,73 +769,70 @@ var _PerfService = function _PerfService
 
 var perfService = new _PerfService();
 module.exports = {
   _PerfService,
   perfService
 };
 
 /***/ }),
-/* 7 */
+/* 8 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(1);
-
-const ac = _require.actionCreators,
-      at = _require.actionTypes;
-
+const React = __webpack_require__(1);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
 
-const LinkMenu = __webpack_require__(8);
-
-var _require2 = __webpack_require__(4);
+const LinkMenu = __webpack_require__(9);
 
-const TOP_SITES_SOURCE = _require2.TOP_SITES_SOURCE,
-      TOP_SITES_CONTEXT_MENU_OPTIONS = _require2.TOP_SITES_CONTEXT_MENU_OPTIONS;
-
+const { TOP_SITES_SOURCE, TOP_SITES_CONTEXT_MENU_OPTIONS, MIN_RICH_FAVICON_SIZE, MIN_CORNER_FAVICON_SIZE } = __webpack_require__(5);
 
 const TopSiteLink = props => {
-  const link = props.link;
-
+  const { link } = props;
   const topSiteOuterClassName = `top-site-outer${props.className ? ` ${props.className}` : ""}`;
-  const tippyTopIcon = link.tippyTopIcon;
-
+  const { tippyTopIcon, faviconSize } = link;
   let imageClassName;
   let imageStyle;
-  if (tippyTopIcon) {
-    imageClassName = "tippy-top-icon";
+  let showSmallFavicon = false;
+  let smallFaviconStyle;
+  if (tippyTopIcon || faviconSize >= MIN_RICH_FAVICON_SIZE) {
+    // styles and class names for top sites with rich icons
+    imageClassName = "top-site-icon rich-icon";
     imageStyle = {
       backgroundColor: link.backgroundColor,
-      backgroundImage: `url(${tippyTopIcon})`
+      backgroundImage: `url(${tippyTopIcon || link.favicon})`
     };
   } else {
+    // styles and class names for top sites with screenshot + small icon in top left corner
     imageClassName = `screenshot${link.screenshot ? " active" : ""}`;
     imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
+
+    // only show a favicon in top left if it's greater than 32x32
+    if (faviconSize >= MIN_CORNER_FAVICON_SIZE) {
+      showSmallFavicon = true;
+      smallFaviconStyle = { backgroundImage: `url(${link.favicon})` };
+    }
   }
   return React.createElement(
     "li",
     { className: topSiteOuterClassName, key: link.guid || link.url },
     React.createElement(
       "a",
       { href: link.url, onClick: props.onClick },
       React.createElement(
         "div",
         { className: "tile", "aria-hidden": true },
         React.createElement(
           "span",
           { className: "letter-fallback" },
           props.title[0]
         ),
-        React.createElement("div", { className: imageClassName, style: imageStyle })
+        React.createElement("div", { className: imageClassName, style: imageStyle }),
+        showSmallFavicon && React.createElement("div", { className: "top-site-icon default-icon", style: smallFaviconStyle })
       ),
       React.createElement(
         "div",
         { className: `title ${link.isPinned ? "pinned" : ""}` },
         link.isPinned && React.createElement("div", { className: "icon icon-pin-small" }),
         React.createElement(
           "span",
           { dir: "auto" },
@@ -890,35 +883,31 @@ class TopSite extends React.Component {
   onMenuButtonClick(event) {
     event.preventDefault();
     this.toggleContextMenu(event, this.props.index);
   }
   onMenuUpdate(showContextMenu) {
     this.setState({ showContextMenu });
   }
   onDismissButtonClick() {
-    const link = this.props.link;
-
+    const { link } = this.props;
     if (link.isPinned) {
       this.props.dispatch(ac.SendToMain({
         type: at.TOP_SITES_UNPIN,
         data: { site: { url: link.url } }
       }));
     }
     this.props.dispatch(ac.SendToMain({
       type: at.BLOCK_URL,
       data: link.url
     }));
     this.userEvent("BLOCK");
   }
   onPinButtonClick() {
-    var _props = this.props;
-    const link = _props.link,
-          index = _props.index;
-
+    const { link, index } = this.props;
     if (link.isPinned) {
       this.props.dispatch(ac.SendToMain({
         type: at.TOP_SITES_UNPIN,
         data: { site: { url: link.url } }
       }));
       this.userEvent("UNPIN");
     } else {
       this.props.dispatch(ac.SendToMain({
@@ -927,19 +916,18 @@ class TopSite extends React.Component {
       }));
       this.userEvent("PIN");
     }
   }
   onEditButtonClick() {
     this.props.onEdit(this.props.index);
   }
   render() {
-    const props = this.props;
-    const link = props.link;
-
+    const { props } = this;
+    const { link } = props;
     const isContextMenuOpen = this.state.showContextMenu && this.state.activeTile === props.index;
     const title = link.label || link.hostname;
     return React.createElement(
       TopSiteLink,
       _extends({}, props, { onClick: this.onLinkClick, className: isContextMenuOpen ? "active" : "", title: title }),
       !props.editMode && React.createElement(
         "div",
         null,
@@ -988,55 +976,36 @@ TopSite.defaultProps = {
 
 const TopSitePlaceholder = () => React.createElement(TopSiteLink, { className: "placeholder" });
 
 module.exports.TopSite = TopSite;
 module.exports.TopSiteLink = TopSiteLink;
 module.exports.TopSitePlaceholder = TopSitePlaceholder;
 
 /***/ }),
-/* 8 */
+/* 9 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(2);
-
-const injectIntl = _require.injectIntl;
-
+const React = __webpack_require__(1);
+const { injectIntl } = __webpack_require__(2);
 const ContextMenu = __webpack_require__(17);
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators;
-
+const { actionCreators: ac } = __webpack_require__(0);
 const linkMenuOptions = __webpack_require__(18);
 const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
 
 class LinkMenu extends React.Component {
   getOptions() {
     const props = this.props;
-    const site = props.site,
-          index = props.index,
-          source = props.source;
+    const { site, index, source } = props;
 
     // Handle special case of default site
-
     const propOptions = !site.isDefault ? props.options : DEFAULT_SITE_MENU_OPTIONS;
 
     const options = propOptions.map(o => linkMenuOptions[o](site, index, source)).map(option => {
-      const action = option.action,
-            impression = option.impression,
-            id = option.id,
-            type = option.type,
-            userEvent = option.userEvent;
-
+      const { action, impression, id, type, userEvent } = option;
       if (!type && id) {
         option.label = props.intl.formatMessage(option);
         option.onClick = () => {
           props.dispatch(action);
           if (userEvent) {
             props.dispatch(ac.UserEvent({
               event: userEvent,
               source,
@@ -1065,214 +1034,154 @@ class LinkMenu extends React.Component {
       options: this.getOptions() });
   }
 }
 
 module.exports = injectIntl(LinkMenu);
 module.exports._unconnected = LinkMenu;
 
 /***/ }),
-/* 9 */
-/***/ (function(module, exports) {
-
-var g;
-
-// This works in non-strict mode
-g = (function() {
-	return this;
-})();
-
-try {
-	// This works if eval is allowed (see CSP)
-	g = g || Function("return this")() || (1,eval)("this");
-} catch(e) {
-	// This works if the window reference is available
-	if(typeof window === "object")
-		g = window;
-}
-
-// g can still be undefined, but nothing to do about it...
-// We return undefined, instead of nothing here, so it's
-// easier to handle this case. if(!global) { ...}
-
-module.exports = g;
-
-
-/***/ }),
 /* 10 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
+/* WEBPACK VAR INJECTION */(function(global) {const React = __webpack_require__(1);
 const ReactDOM = __webpack_require__(11);
 const Base = __webpack_require__(12);
-
-var _require = __webpack_require__(3);
-
-const Provider = _require.Provider;
-
+const { Provider } = __webpack_require__(3);
 const initStore = __webpack_require__(28);
-
-var _require2 = __webpack_require__(5);
-
-const reducers = _require2.reducers;
-
+const { reducers } = __webpack_require__(6);
 const DetectUserSessionStart = __webpack_require__(30);
-
-var _require3 = __webpack_require__(31);
-
-const addSnippetsSubscriber = _require3.addSnippetsSubscriber;
-
+const { addSnippetsSubscriber } = __webpack_require__(31);
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
 
 new DetectUserSessionStart().sendEventOrAddListener();
 
-const store = initStore(reducers);
+const store = initStore(reducers, global.gActivityStreamPrerenderedState);
+
+// If we are starting in a prerendered state, we must wait until the first render
+// to request state rehydration (see Base.jsx). If we are NOT in a prerendered state,
+// we can request it immedately.
+if (!global.gActivityStreamPrerenderedState) {
+  store.dispatch(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+}
 
 ReactDOM.render(React.createElement(
   Provider,
   { store: store },
-  React.createElement(Base, null)
+  React.createElement(Base, { isPrerendered: !!global.gActivityStreamPrerenderedState })
 ), document.getElementById("root"));
 
 addSnippetsSubscriber(store);
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
 
 /***/ }),
 /* 11 */
 /***/ (function(module, exports) {
 
 module.exports = ReactDOM;
 
 /***/ }),
 /* 12 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const addLocaleData = _require2.addLocaleData,
-      IntlProvider = _require2.IntlProvider;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { addLocaleData, IntlProvider } = __webpack_require__(2);
 const TopSites = __webpack_require__(13);
 const Search = __webpack_require__(19);
 const ConfirmDialog = __webpack_require__(21);
 const ManualMigration = __webpack_require__(22);
 const PreferencesPane = __webpack_require__(23);
 const Sections = __webpack_require__(24);
-
-// Locales that should be displayed RTL
-const RTL_LIST = ["ar", "he", "fa", "ur"];
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
 
 // Add the locale data for pluralization and relative-time formatting for now,
 // this just uses english locale data. We can make this more sophisticated if
 // more features are needed.
-function addLocaleDataForReactIntl(_ref) {
-  let locale = _ref.locale;
-
+function addLocaleDataForReactIntl({ locale, textDirection }) {
   addLocaleData([{ locale, parentLocale: "en" }]);
   document.documentElement.lang = locale;
-  document.documentElement.dir = RTL_LIST.indexOf(locale.split("-")[0]) >= 0 ? "rtl" : "ltr";
+  document.documentElement.dir = textDirection;
 }
 
 class Base extends React.Component {
   componentDidMount() {
+    // Request state AFTER the first render to ensure we don't cause the
+    // prerendered DOM to be unmounted. Otherwise, NEW_TAB_STATE_REQUEST is
+    // dispatched right after the store is ready.
+    if (this.props.isPrerendered) {
+      this.props.dispatch(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+    }
+
     // Also wait for the preloaded page to show, so the tab's title and favicon updates
     addEventListener("visibilitychange", () => {
       this.updateTitle(this.props.App);
       document.getElementById("favicon").href += "#";
     }, { once: true });
   }
-  componentWillUpdate(_ref2) {
-    let App = _ref2.App;
-
+  componentWillUpdate({ App }) {
     // Early loads might not have locale yet, so wait until we do
     if (App.locale && App.locale !== this.props.App.locale) {
       addLocaleDataForReactIntl(App);
       this.updateTitle(App);
     }
   }
 
-  updateTitle(_ref3) {
-    let strings = _ref3.strings;
-
+  updateTitle({ strings }) {
     if (strings) {
       document.title = strings.newtab_page_title;
     }
   }
 
   render() {
     const props = this.props;
-    var _props$App = props.App;
-    const locale = _props$App.locale,
-          strings = _props$App.strings,
-          initialized = _props$App.initialized;
+    const { locale, strings, initialized } = props.App;
+    const prefs = props.Prefs.values;
 
-    const prefs = props.Prefs.values;
-    if (!initialized || !strings) {
+    if (!props.isPrerendered && !initialized) {
       return null;
     }
 
+    // Note: the key on IntlProvider must be static in order to not blow away
+    // all elements on a locale change (such as after preloading).
+    // See https://github.com/yahoo/react-intl/issues/695 for more info.
     return React.createElement(
       IntlProvider,
-      { key: locale, locale: locale, messages: strings },
+      { key: "STATIC", locale: locale, messages: strings },
       React.createElement(
         "div",
         { className: "outer-wrapper" },
         React.createElement(
           "main",
           null,
           prefs.showSearch && React.createElement(Search, null),
           !prefs.migrationExpired && React.createElement(ManualMigration, null),
           prefs.showTopSites && React.createElement(TopSites, null),
           React.createElement(Sections, null),
           React.createElement(ConfirmDialog, null)
         ),
-        React.createElement(PreferencesPane, null)
+        initialized && React.createElement(PreferencesPane, null)
       )
     );
   }
 }
 
 module.exports = connect(state => ({ App: state.App, Prefs: state.Prefs }))(Base);
 
 /***/ }),
 /* 13 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage } = __webpack_require__(2);
 
 const TopSitesPerfTimer = __webpack_require__(14);
 const TopSitesEdit = __webpack_require__(15);
-
-var _require3 = __webpack_require__(7);
-
-const TopSite = _require3.TopSite,
-      TopSitePlaceholder = _require3.TopSitePlaceholder;
-
+const { TopSite, TopSitePlaceholder } = __webpack_require__(8);
 
 const TopSites = props => {
   const realTopSites = props.TopSites.rows.slice(0, props.TopSitesCount);
   const placeholderCount = props.TopSitesCount - realTopSites.length;
   return React.createElement(
     TopSitesPerfTimer,
     null,
     React.createElement(
@@ -1302,33 +1211,20 @@ const TopSites = props => {
 
 module.exports = connect(state => ({ TopSites: state.TopSites, TopSitesCount: state.Prefs.values.topSitesCount }))(TopSites);
 module.exports._unconnected = TopSites;
 
 /***/ }),
 /* 14 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators,
-      at = _require2.actionTypes;
-
-var _require3 = __webpack_require__(6);
-
-const perfSvc = _require3.perfService;
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
+const { perfService: perfSvc } = __webpack_require__(7);
 
 /**
  * A proxy class that uses double requestAnimationFrame from
  * componentDidMount to dispatch a SAVE_SESSION_PERF_DATA to the main procsess
  * after the paint.
  *
  * This uses two callbacks because, after one callback, this part of the tree
  * may have rendered but not yet reflowed.  This strategy is modeled after
@@ -1338,17 +1234,16 @@ const perfSvc = _require3.perfService;
  * make it lag too long.
  *
  * XXX Should be made more generic by using this.props.children, or potentially
  * even split out into a higher-order component to wrap whatever.
  *
  * @class TopSitesPerfTimer
  * @extends {React.Component}
  */
-
 class TopSitesPerfTimer extends React.Component {
   constructor(props) {
     super(props);
     // Just for test dependency injection:
     this.perfSvc = this.props.perfSvc || perfSvc;
 
     this._sendPaintedEvent = this._sendPaintedEvent.bind(this);
     this._timestampHandled = false;
@@ -1435,48 +1330,25 @@ class TopSitesPerfTimer extends React.Co
 
 module.exports = connect(state => ({ TopSites: state.TopSites }))(TopSitesPerfTimer);
 module.exports._unconnected = TopSitesPerfTimer;
 
 /***/ }),
 /* 15 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(2);
-
-const FormattedMessage = _require.FormattedMessage,
-      injectIntl = _require.injectIntl;
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators,
-      at = _require2.actionTypes;
-
+const React = __webpack_require__(1);
+const { FormattedMessage, injectIntl } = __webpack_require__(2);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
 
 const TopSiteForm = __webpack_require__(16);
-
-var _require3 = __webpack_require__(7);
-
-const TopSite = _require3.TopSite,
-      TopSitePlaceholder = _require3.TopSitePlaceholder;
-
-var _require4 = __webpack_require__(5);
+const { TopSite, TopSitePlaceholder } = __webpack_require__(8);
 
-const TOP_SITES_DEFAULT_LENGTH = _require4.TOP_SITES_DEFAULT_LENGTH,
-      TOP_SITES_SHOWMORE_LENGTH = _require4.TOP_SITES_SHOWMORE_LENGTH;
-
-var _require5 = __webpack_require__(4);
-
-const TOP_SITES_SOURCE = _require5.TOP_SITES_SOURCE;
-
+const { TOP_SITES_DEFAULT_LENGTH, TOP_SITES_SHOWMORE_LENGTH } = __webpack_require__(6);
+const { TOP_SITES_SOURCE } = __webpack_require__(5);
 
 class TopSitesEdit extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
       showEditModal: false,
       showAddForm: false,
       showEditForm: false,
@@ -1618,50 +1490,38 @@ class TopSitesEdit extends React.Compone
         React.createElement(
           "div",
           { className: "modal" },
           React.createElement(TopSiteForm, {
             label: this.props.TopSites.rows[this.state.editIndex].label || this.props.TopSites.rows[this.state.editIndex].hostname,
             url: this.props.TopSites.rows[this.state.editIndex].url,
             index: this.state.editIndex,
             editMode: true,
+            link: this.props.TopSites.rows[this.state.editIndex],
             onClose: this.onFormClose,
             dispatch: this.props.dispatch,
             intl: this.props.intl })
         )
       )
     );
   }
 }
 
 module.exports = injectIntl(TopSitesEdit);
 module.exports._unconnected = TopSitesEdit;
 
 /***/ }),
 /* 16 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(1);
+const React = __webpack_require__(1);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
+const { FormattedMessage } = __webpack_require__(2);
 
-const ac = _require.actionCreators,
-      at = _require.actionTypes;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(4);
-
-const TOP_SITES_SOURCE = _require3.TOP_SITES_SOURCE;
-
+const { TOP_SITES_SOURCE } = __webpack_require__(5);
 
 class TopSiteForm extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
       label: props.label || "",
       url: props.url || "",
       validationError: false
@@ -1705,16 +1565,23 @@ class TopSiteForm extends React.Componen
   }
   onSaveButtonClick(ev) {
     ev.preventDefault();
     if (this.validateForm()) {
       let site = { url: this.cleanUrl() };
       if (this.state.label !== "") {
         site.label = this.state.label;
       }
+      // Unpin links if the URL changed.
+      if (this.props.link.isPinned && this.props.link.url !== site.url) {
+        this.props.dispatch(ac.SendToMain({
+          type: at.TOP_SITES_UNPIN,
+          data: { site: { url: this.props.link.url } }
+        }));
+      }
       this.props.dispatch(ac.SendToMain({
         type: at.TOP_SITES_PIN,
         data: { site, index: this.props.index }
       }));
       this.props.dispatch(ac.UserEvent({
         source: TOP_SITES_SOURCE,
         event: "TOP_SITES_EDIT",
         action_position: this.props.index
@@ -1827,20 +1694,17 @@ TopSiteForm.defaultProps = {
 };
 
 module.exports = TopSiteForm;
 
 /***/ }),
 /* 17 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
+const React = __webpack_require__(1);
 
 class ContextMenu extends React.Component {
   constructor(props) {
     super(props);
     this.hideContext = this.hideContext.bind(this);
   }
   hideContext() {
     this.props.onUpdate(false);
@@ -1880,36 +1744,34 @@ class ContextMenuItem extends React.Comp
     this.onClick = this.onClick.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
   }
   onClick() {
     this.props.hideContext();
     this.props.option.onClick();
   }
   onKeyDown(event) {
-    const option = this.props.option;
-
+    const { option } = this.props;
     switch (event.key) {
       case "Tab":
         // tab goes down in context menu, shift + tab goes up in context menu
         // if we're on the last item, one more tab will close the context menu
         // similarly, if we're on the first item, one more shift + tab will close it
         if (event.shiftKey && option.first || !event.shiftKey && option.last) {
           this.props.hideContext();
         }
         break;
       case "Enter":
         this.props.hideContext();
         option.onClick();
         break;
     }
   }
   render() {
-    const option = this.props.option;
-
+    const { option } = this.props;
     return React.createElement(
       "li",
       { role: "menuitem", className: "context-menu-item" },
       React.createElement(
         "a",
         { onClick: this.onClick, onKeyDown: this.onKeyDown, tabIndex: "0" },
         option.icon && React.createElement("span", { className: `icon icon-spacer icon-${option.icon}` }),
         option.label
@@ -1921,30 +1783,23 @@ class ContextMenuItem extends React.Comp
 module.exports = ContextMenu;
 module.exports.ContextMenu = ContextMenu;
 module.exports.ContextMenuItem = ContextMenuItem;
 
 /***/ }),
 /* 18 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-var _require = __webpack_require__(1);
-
-const at = _require.actionTypes,
-      ac = _require.actionCreators;
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
 
 /**
  * List of functions that return items that can be included as menu options in a
  * LinkMenu. All functions take the site as the first parameter, and optionally
  * the index of the site.
  */
-
 module.exports = {
   Separator: () => ({ type: "separator" }),
   RemoveBookmark: site => ({
     id: "menu_action_remove_bookmark",
     icon: "bookmark-added",
     action: ac.SendToMain({
       type: at.DELETE_BOOKMARK_BY_ID,
       data: site.bookmarkGuid
@@ -2047,35 +1902,21 @@ module.exports.CheckPinTopSite = (site, 
 /***/ }),
 /* 19 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 /* globals ContentSearchUIController */
 
 
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage,
-      injectIntl = _require2.injectIntl;
-
-var _require3 = __webpack_require__(1);
-
-const ac = _require3.actionCreators;
-
-var _require4 = __webpack_require__(20);
-
-const IS_NEWTAB = _require4.IS_NEWTAB;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage, injectIntl } = __webpack_require__(2);
+const { actionCreators: ac } = __webpack_require__(0);
+const { IS_NEWTAB } = __webpack_require__(20);
 
 class Search extends React.Component {
   constructor(props) {
     super(props);
     this.onClick = this.onClick.bind(this);
     this.onInputMount = this.onInputMount.bind(this);
   }
 
@@ -2155,52 +1996,38 @@ class Search extends React.Component {
           { className: "sr-only" },
           React.createElement(FormattedMessage, { id: "search_button" })
         )
       )
     );
   }
 }
 
-module.exports = connect()(injectIntl(Search));
+// initialized is passed to props so that Search will rerender when it receives strings
+module.exports = connect(state => ({ locale: state.App.locale }))(injectIntl(Search));
 module.exports._unconnected = Search;
 
 /***/ }),
 /* 20 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-module.exports = {
+/* WEBPACK VAR INJECTION */(function(global) {module.exports = {
   // constant to know if the page is about:newtab or about:home
-  IS_NEWTAB: document.documentURI === "about:newtab"
+  IS_NEWTAB: global.document && global.document.documentURI === "about:newtab"
 };
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
 
 /***/ }),
 /* 21 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(1);
-
-const actionTypes = _require3.actionTypes,
-      ac = _require3.actionCreators;
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage } = __webpack_require__(2);
+const { actionTypes, actionCreators: ac } = __webpack_require__(0);
 
 /**
  * ConfirmDialog component.
  * One primary action button, one cancel button.
  *
  * Content displayed is controlled by `data` prop the component receives.
  * Example:
  * data: {
@@ -2211,17 +2038,16 @@ const actionTypes = _require3.actionType
  *   // Primary button USerEvent action.
  *   userEvent: "DELETE",
  *   // Array of locale ids to display.
  *   message_body: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
  *   // Text for primary button.
  *   confirm_button_string_id: "menu_action_delete"
  * },
  */
-
 const ConfirmDialog = React.createClass({
   displayName: "ConfirmDialog",
 
   getDefaultProps() {
     return {
       visible: false,
       data: {}
     };
@@ -2293,43 +2119,29 @@ const ConfirmDialog = React.createClass(
 module.exports = connect(state => state.Dialog)(ConfirmDialog);
 module.exports._unconnected = ConfirmDialog;
 module.exports.Dialog = ConfirmDialog;
 
 /***/ }),
 /* 22 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(1);
-
-const at = _require3.actionTypes,
-      ac = _require3.actionCreators;
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { FormattedMessage } = __webpack_require__(2);
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
 
 /**
  * Manual migration component used to start the profile import wizard.
  * Message is presented temporarily and will go away if:
  * 1.  User clicks "No Thanks"
  * 2.  User completed the data import
  * 3.  After 3 active days
  * 4.  User clicks "Cancel" on the import wizard (currently not implemented).
  */
-
 class ManualMigration extends React.Component {
   constructor(props) {
     super(props);
     this.onLaunchTour = this.onLaunchTour.bind(this);
     this.onCancelTour = this.onCancelTour.bind(this);
   }
   onLaunchTour() {
     this.props.dispatch(ac.SendToMain({ type: at.MIGRATION_START }));
@@ -2371,40 +2183,21 @@ class ManualMigration extends React.Comp
 
 module.exports = connect()(ManualMigration);
 module.exports._unconnected = ManualMigration;
 
 /***/ }),
 /* 23 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
-
-const injectIntl = _require2.injectIntl,
-      FormattedMessage = _require2.FormattedMessage;
-
-var _require3 = __webpack_require__(1);
-
-const ac = _require3.actionCreators,
-      at = _require3.actionTypes;
-
-var _require4 = __webpack_require__(5);
-
-const TOP_SITES_DEFAULT_LENGTH = _require4.TOP_SITES_DEFAULT_LENGTH,
-      TOP_SITES_SHOWMORE_LENGTH = _require4.TOP_SITES_SHOWMORE_LENGTH;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { injectIntl, FormattedMessage } = __webpack_require__(2);
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
+const { TOP_SITES_DEFAULT_LENGTH, TOP_SITES_SHOWMORE_LENGTH } = __webpack_require__(6);
 
 const getFormattedMessage = message => typeof message === "string" ? React.createElement(
   "span",
   null,
   message
 ) : React.createElement(FormattedMessage, message);
 
 const PreferencesInput = props => React.createElement(
@@ -2442,19 +2235,17 @@ class PreferencesPane extends React.Comp
   handleClickOutside(event) {
     // if we are showing the sidebar and there is a click outside, close it.
     if (this.state.visible && !this.wrapper.contains(event.target)) {
       this.togglePane();
     }
   }
   handlePrefChange(event) {
     const target = event.target;
-    const name = target.name,
-          checked = target.checked;
-
+    const { name, checked } = target;
     let value = checked;
     if (name === "topSitesCount") {
       value = checked ? TOP_SITES_SHOWMORE_LENGTH : TOP_SITES_DEFAULT_LENGTH;
     }
     this.props.dispatch(ac.SetPref(name, value));
   }
   handleSectionChange(event) {
     const target = event.target;
@@ -2511,25 +2302,24 @@ class PreferencesPane extends React.Comp
             React.createElement(PreferencesInput, { className: "showTopSites", prefName: "showTopSites", value: prefs.showTopSites, onChange: this.handlePrefChange,
               titleString: { id: "settings_pane_topsites_header" }, descString: { id: "settings_pane_topsites_body" } }),
             React.createElement(
               "div",
               { className: "options" },
               React.createElement(PreferencesInput, { className: "showMoreTopSites", prefName: "topSitesCount", value: prefs.topSitesCount !== TOP_SITES_DEFAULT_LENGTH, onChange: this.handlePrefChange,
                 titleString: { id: "settings_pane_topsites_options_showmore" }, labelClassName: "icon icon-topsites" })
             ),
-            sections.filter(section => !section.shouldHidePref).map((_ref) => {
-              let id = _ref.id,
-                  title = _ref.title,
-                  enabled = _ref.enabled,
-                  pref = _ref.pref;
-              return React.createElement(PreferencesInput, { key: id, className: "showSection", prefName: pref && pref.feed || id,
-                value: enabled, onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange,
-                titleString: pref && pref.titleString || title, descString: pref && pref.descString });
-            })
+            sections.filter(section => !section.shouldHidePref).map(({ id, title, enabled, pref }) => React.createElement(PreferencesInput, { key: id, className: "showSection", prefName: pref && pref.feed || id,
+              value: enabled, onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange,
+              titleString: pref && pref.titleString || title, descString: pref && pref.descString })),
+            React.createElement("hr", null),
+            React.createElement(PreferencesInput, { className: "showSnippets", prefName: "feeds.snippets",
+              value: prefs["feeds.snippets"], onChange: this.handlePrefChange,
+              titleString: { id: "settings_pane_snippets_header" },
+              descString: { id: "settings_pane_snippets_body" } })
           ),
           React.createElement(
             "section",
             { className: "actions" },
             React.createElement(
               "button",
               { className: "done", onClick: this.togglePane },
               React.createElement(FormattedMessage, { id: "settings_pane_done_button" })
@@ -2544,41 +2334,25 @@ class PreferencesPane extends React.Comp
 module.exports = connect(state => ({ Prefs: state.Prefs, Sections: state.Sections }))(injectIntl(PreferencesPane));
 module.exports.PreferencesPane = PreferencesPane;
 module.exports.PreferencesInput = PreferencesInput;
 
 /***/ }),
 /* 24 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(3);
-
-const connect = _require.connect;
-
-var _require2 = __webpack_require__(2);
+/* WEBPACK VAR INJECTION */(function(global) {var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
-const injectIntl = _require2.injectIntl,
-      FormattedMessage = _require2.FormattedMessage;
-
+const React = __webpack_require__(1);
+const { connect } = __webpack_require__(3);
+const { injectIntl, FormattedMessage } = __webpack_require__(2);
 const Card = __webpack_require__(25);
-const PlaceholderCard = Card.PlaceholderCard;
-
+const { PlaceholderCard } = Card;
 const Topics = __webpack_require__(27);
-
-var _require3 = __webpack_require__(1);
-
-const ac = _require3.actionCreators;
-
+const { actionCreators: ac } = __webpack_require__(0);
 
 const VISIBLE = "visible";
 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
 const CARDS_PER_ROW = 3;
 
 class Section extends React.Component {
   constructor(props) {
     super(props);
@@ -2613,32 +2387,30 @@ class Section extends React.Component {
     return typeof message === "string" ? React.createElement(
       "span",
       null,
       message
     ) : React.createElement(FormattedMessage, message);
   }
 
   _dispatchImpressionStats() {
-    const props = this.props;
-
+    const { props } = this;
     const maxCards = 3 * props.maxRows;
     props.dispatch(ac.ImpressionStats({
       source: props.eventSource,
       tiles: props.rows.slice(0, maxCards).map(link => ({ id: link.guid })),
       incognito: props.options && props.options.personalized
     }));
   }
 
   // This sends an event when a user sees a set of new content. If content
   // changes while the page is hidden (i.e. preloaded or on a hidden tab),
   // only send the event if the page becomes visible again.
   sendImpressionStatsOrAddListener() {
-    const props = this.props;
-
+    const { props } = this;
 
     if (!props.dispatch) {
       return;
     }
 
     if (props.document.visibilityState === VISIBLE) {
       this._dispatchImpressionStats();
     } else {
@@ -2661,18 +2433,17 @@ class Section extends React.Component {
 
   componentDidMount() {
     if (this.props.rows.length) {
       this.sendImpressionStatsOrAddListener();
     }
   }
 
   componentDidUpdate(prevProps) {
-    const props = this.props;
-
+    const { props } = this;
     if (
     // Don't send impression stats for the empty state
     props.rows.length &&
     // We only want to send impression stats if the content of the cards has changed
     props.rows !== prevProps.rows) {
       this.sendImpressionStatsOrAddListener();
     }
   }
@@ -2684,32 +2455,23 @@ class Section extends React.Component {
     const remainder = items % CARDS_PER_ROW;
     if (remainder === 0) {
       return 0;
     }
     return CARDS_PER_ROW - remainder;
   }
 
   render() {
-    var _props = this.props;
-    const id = _props.id,
-          eventSource = _props.eventSource,
-          title = _props.title,
-          icon = _props.icon,
-          rows = _props.rows,
-          infoOption = _props.infoOption,
-          emptyState = _props.emptyState,
-          dispatch = _props.dispatch,
-          maxRows = _props.maxRows,
-          contextMenuOptions = _props.contextMenuOptions,
-          intl = _props.intl,
-          initialized = _props.initialized;
-
+    const {
+      id, eventSource, title, icon, rows,
+      infoOption, emptyState, dispatch, maxRows,
+      contextMenuOptions, intl, initialized
+    } = this.props;
     const maxCards = CARDS_PER_ROW * maxRows;
-    const shouldShowTopics = id === "topstories" && this.props.topics && this.props.topics.length > 0 && this.props.read_more_endpoint;
+    const shouldShowTopics = id === "topstories" && this.props.topics && this.props.topics.length > 0;
 
     const infoOptionIconA11yAttrs = {
       "aria-haspopup": "true",
       "aria-controls": "info-option",
       "aria-expanded": this.state.infoActive ? "true" : "false",
       "role": "note",
       "tabIndex": 0
     };
@@ -2811,49 +2573,37 @@ class Sections extends React.Component {
     );
   }
 }
 
 module.exports = connect(state => ({ Sections: state.Sections }))(Sections);
 module.exports._unconnected = Sections;
 module.exports.SectionIntl = SectionIntl;
 module.exports._unconnectedSection = Section;
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)))
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
 
 /***/ }),
 /* 25 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-const LinkMenu = __webpack_require__(8);
-
-var _require = __webpack_require__(2);
-
-const FormattedMessage = _require.FormattedMessage;
-
+const React = __webpack_require__(1);
+const LinkMenu = __webpack_require__(9);
+const { FormattedMessage } = __webpack_require__(2);
 const cardContextTypes = __webpack_require__(26);
-
-var _require2 = __webpack_require__(1);
-
-const ac = _require2.actionCreators,
-      at = _require2.actionTypes;
+const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
 
 /**
  * Card component.
  * Cards are found within a Section component and contain information about a link such
  * as preview image, page title, page description, and some context about if the page
  * was visited, bookmarked, trending etc...
  * Each Section can make an unordered list of Cards which will create one instane of
  * this class. Each card will then get a context menu which reflects the actions that
  * can be done on this Card.
  */
-
 class Card extends React.Component {
   constructor(props) {
     super(props);
     this.state = { showContextMenu: false, activeCard: null };
     this.onMenuButtonClick = this.onMenuButtonClick.bind(this);
     this.onMenuUpdate = this.onMenuUpdate.bind(this);
     this.onLinkClick = this.onLinkClick.bind(this);
   }
@@ -2861,22 +2611,17 @@ class Card extends React.Component {
     event.preventDefault();
     this.setState({
       activeCard: this.props.index,
       showContextMenu: true
     });
   }
   onLinkClick(event) {
     event.preventDefault();
-    const altKey = event.altKey,
-          button = event.button,
-          ctrlKey = event.ctrlKey,
-          metaKey = event.metaKey,
-          shiftKey = event.shiftKey;
-
+    const { altKey, button, ctrlKey, metaKey, shiftKey } = event;
     this.props.dispatch(ac.SendToMain({
       type: at.OPEN_LINK,
       data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
     }));
     this.props.dispatch(ac.UserEvent({
       event: "CLICK",
       source: this.props.eventSource,
       action_position: this.props.index
@@ -2887,31 +2632,21 @@ class Card extends React.Component {
       incognito: true,
       tiles: [{ id: this.props.link.guid, pos: this.props.index }]
     }));
   }
   onMenuUpdate(showContextMenu) {
     this.setState({ showContextMenu });
   }
   render() {
-    var _props = this.props;
-    const index = _props.index,
-          link = _props.link,
-          dispatch = _props.dispatch,
-          contextMenuOptions = _props.contextMenuOptions,
-          eventSource = _props.eventSource;
-    const props = this.props;
-
+    const { index, link, dispatch, contextMenuOptions, eventSource } = this.props;
+    const { props } = this;
     const isContextMenuOpen = this.state.showContextMenu && this.state.activeCard === index;
-
-    var _ref = link.type ? cardContextTypes[link.type] : {};
-
-    const icon = _ref.icon,
-          intlID = _ref.intlID;
-
+    // Display "now" as "trending" until we have new strings #3402
+    const { icon, intlID } = cardContextTypes[link.type === "now" ? "trending" : link.type] || {};
 
     return React.createElement(
       "li",
       { className: `card-outer${isContextMenuOpen ? " active" : ""}${props.placeholder ? " placeholder" : ""}` },
       React.createElement(
         "a",
         { href: link.url, onClick: !props.placeholder && this.onLinkClick },
         React.createElement(
@@ -2978,20 +2713,17 @@ Card.defaultProps = { link: {} };
 
 const PlaceholderCard = () => React.createElement(Card, { placeholder: true });
 
 module.exports = Card;
 module.exports.PlaceholderCard = PlaceholderCard;
 
 /***/ }),
 /* 26 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
+/***/ (function(module, exports) {
 
 module.exports = {
   history: {
     intlID: "type_label_visited",
     icon: "historyItem"
   },
   bookmark: {
     intlID: "type_label_bookmarked",
@@ -3006,95 +2738,71 @@ module.exports = {
     icon: "now"
   }
 };
 
 /***/ }),
 /* 27 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-const React = __webpack_require__(0);
-
-var _require = __webpack_require__(2);
-
-const FormattedMessage = _require.FormattedMessage;
-
+const React = __webpack_require__(1);
+const { FormattedMessage } = __webpack_require__(2);
 
 class Topic extends React.Component {
   render() {
-    var _props = this.props;
-    const url = _props.url,
-          name = _props.name;
-
+    const { url, name } = this.props;
     return React.createElement(
       "li",
       null,
       React.createElement(
         "a",
         { key: name, className: "topic-link", href: url },
         name
       )
     );
   }
 }
 
 class Topics extends React.Component {
   render() {
-    var _props2 = this.props;
-    const topics = _props2.topics,
-          read_more_endpoint = _props2.read_more_endpoint;
-
+    const { topics, read_more_endpoint } = this.props;
     return React.createElement(
       "div",
       { className: "topic" },
       React.createElement(
         "span",
         null,
         React.createElement(FormattedMessage, { id: "pocket_read_more" })
       ),
       React.createElement(
         "ul",
         null,
         topics.map(t => React.createElement(Topic, { key: t.name, url: t.url, name: t.name }))
       ),
-      React.createElement(
+      read_more_endpoint && React.createElement(
         "a",
         { className: "topic-read-more", href: read_more_endpoint },
         React.createElement(FormattedMessage, { id: "pocket_read_even_more" })
       )
     );
   }
 }
 
 module.exports = Topics;
 module.exports._unconnected = Topics;
 module.exports.Topic = Topic;
 
 /***/ }),
 /* 28 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-/* eslint-env mozilla/frame-script */
-
-var _require = __webpack_require__(29);
+/* WEBPACK VAR INJECTION */(function(global) {/* eslint-env mozilla/frame-script */
 
-const createStore = _require.createStore,
-      combineReducers = _require.combineReducers,
-      applyMiddleware = _require.applyMiddleware;
-
-var _require2 = __webpack_require__(1);
-
-const au = _require2.actionUtils;
-
+const { createStore, combineReducers, applyMiddleware } = __webpack_require__(29);
+const { actionTypes: at, actionCreators: ac, actionUtils: au } = __webpack_require__(0);
 
 const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
 const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
 const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
 
 /**
  * A higher-order function which returns a reducer that, on MERGE_STORE action,
  * will return the action.data object merged into the previous state.
@@ -3126,73 +2834,103 @@ function mergeStateReducer(mainReducer) 
  */
 const messageMiddleware = store => next => action => {
   if (au.isSendToMain(action)) {
     sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
   }
   next(action);
 };
 
+const rehydrationMiddleware = store => next => action => {
+  if (store._didRehydrate) {
+    return next(action);
+  }
+
+  const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
+  const isRehydrationRequest = action.type === at.NEW_TAB_STATE_REQUEST;
+
+  if (isRehydrationRequest) {
+    store._didRequestInitialState = true;
+    return next(action);
+  }
+
+  if (isMergeStoreAction) {
+    store._didRehydrate = true;
+    return next(action);
+  }
+
+  // If init happened after our request was made, we need to re-request
+  if (store._didRequestInitialState && action.type === at.INIT) {
+    return next(ac.SendToMain({ type: at.NEW_TAB_STATE_REQUEST }));
+  }
+
+  if (au.isBroadcastToContent(action) || au.isSendToContent(action)) {
+    // Note that actions received before didRehydrate will not be dispatched
+    // because this could negatively affect preloading and the the state
+    // will be replaced by rehydration anyway.
+    return null;
+  }
+
+  return next(action);
+};
+
 /**
  * initStore - Create a store and listen for incoming actions
  *
  * @param  {object} reducers An object containing Redux reducers
+ * @param  {object} intialState (optional) The initial state of the store, if desired
  * @return {object}          A redux store
  */
-module.exports = function initStore(reducers) {
-  const store = createStore(mergeStateReducer(combineReducers(reducers)), applyMiddleware(messageMiddleware));
+module.exports = function initStore(reducers, initialState) {
+  const store = createStore(mergeStateReducer(combineReducers(reducers)), initialState, global.addMessageListener && applyMiddleware(rehydrationMiddleware, messageMiddleware));
+
+  store._didRehydrate = false;
+  store._didRequestInitialState = false;
 
-  addMessageListener(INCOMING_MESSAGE_NAME, msg => {
-    try {
-      store.dispatch(msg.data);
-    } catch (ex) {
-      console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console
-      dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`);
-    }
-  });
+  if (global.addMessageListener) {
+    global.addMessageListener(INCOMING_MESSAGE_NAME, msg => {
+      try {
+        store.dispatch(msg.data);
+      } catch (ex) {
+        console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console
+        dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`);
+      }
+    });
+  }
 
   return store;
 };
 
+module.exports.rehydrationMiddleware = rehydrationMiddleware;
 module.exports.MERGE_STORE_ACTION = MERGE_STORE_ACTION;
 module.exports.OUTGOING_MESSAGE_NAME = OUTGOING_MESSAGE_NAME;
 module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
 
 /***/ }),
 /* 29 */
 /***/ (function(module, exports) {
 
 module.exports = Redux;
 
 /***/ }),
 /* 30 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-
-
-var _require = __webpack_require__(1);
-
-const at = _require.actionTypes;
-
-var _require2 = __webpack_require__(6);
-
-const perfSvc = _require2.perfService;
-
+/* WEBPACK VAR INJECTION */(function(global) {const { actionTypes: at } = __webpack_require__(0);
+const { perfService: perfSvc } = __webpack_require__(7);
 
 const VISIBLE = "visible";
 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
 
 module.exports = class DetectUserSessionStart {
-  constructor() {
-    let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
-
+  constructor(options = {}) {
     // Overrides for testing
-    this.sendAsyncMessage = options.sendAsyncMessage || window.sendAsyncMessage;
-    this.document = options.document || document;
+    this.sendAsyncMessage = options.sendAsyncMessage || global.sendAsyncMessage;
+    this.document = options.document || global.document;
     this._perfService = options.perfService || perfSvc;
     this._onVisibilityChange = this._onVisibilityChange.bind(this);
   }
 
   /**
    * sendEventOrAddListener - Notify immediately if the page is already visible,
    *                    or else set up a listener for when visibility changes.
    *                    This is needed for accurate session tracking for telemetry,
@@ -3236,43 +2974,40 @@ module.exports = class DetectUserSession
    */
   _onVisibilityChange() {
     if (this.document.visibilityState === VISIBLE) {
       this._sendEvent();
       this.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
     }
   }
 };
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
 
 /***/ }),
 /* 31 */
 /***/ (function(module, exports, __webpack_require__) {
 
-"use strict";
-/* WEBPACK VAR INJECTION */(function(global) {
-
-const DATABASE_NAME = "snippets_db";
+/* WEBPACK VAR INJECTION */(function(global) {const DATABASE_NAME = "snippets_db";
 const DATABASE_VERSION = 1;
 const SNIPPETS_OBJECTSTORE_NAME = "snippets";
 const SNIPPETS_UPDATE_INTERVAL_MS = 14400000; // 4 hours.
 
-var _require = __webpack_require__(1);
+const SNIPPETS_ENABLED_EVENT = "Snippets:Enabled";
+const SNIPPETS_DISABLED_EVENT = "Snippets:Disabled";
 
-const at = _require.actionTypes,
-      ac = _require.actionCreators;
+const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
 
 /**
  * SnippetsMap - A utility for cacheing values related to the snippet. It has
  *               the same interface as a Map, but is optionally backed by
  *               indexedDB for persistent storage.
  *               Call .connect() to open a database connection and restore any
  *               previously cached data, if necessary.
  *
  */
-
 class SnippetsMap extends Map {
   constructor(dispatch) {
     super();
     this._db = null;
     this._dispatch = dispatch;
   }
 
   set(key, value) {
@@ -3528,44 +3263,62 @@ class SnippetsProvider {
     await this._refreshSnippets();
 
     // Try showing remote snippets, falling back to defaults if necessary.
     try {
       this._showRemoteSnippets();
     } catch (e) {
       this._showDefaultSnippets(e);
     }
+
+    window.dispatchEvent(new Event(SNIPPETS_ENABLED_EVENT));
+    this.initialized = true;
+  }
+
+  uninit() {
+    window.dispatchEvent(new Event(SNIPPETS_DISABLED_EVENT));
+    this.initialized = false;
   }
 }
 
 /**
  * addSnippetsSubscriber - Creates a SnippetsProvider that Initializes
  *                         when the store has received the appropriate
  *                         Snippet data.
  *
  * @param  {obj} store   The redux store
- * @return {obj}        Returns the snippets instance and unsubscribe function
+ * @return {obj}         Returns the snippets instance and unsubscribe function
  */
 function addSnippetsSubscriber(store) {
   const snippets = new SnippetsProvider(store.dispatch);
-  const unsubscribe = store.subscribe(() => {
+
+  let initializing = false;
+
+  store.subscribe(async () => {
     const state = store.getState();
-    if (state.Snippets.initialized) {
-      if (state.Snippets.onboardingFinished) {
-        snippets.init({ appData: state.Snippets });
+    // state.Snippets.initialized:  Should snippets be initialised?
+    // snippets.initialized:        Is SnippetsProvider currently initialised?
+    if (state.Snippets.initialized && !snippets.initialized && state.Snippets.onboardingFinished) {
+      // Don't call init multiple times
+      if (!initializing) {
+        initializing = true;
+        await snippets.init({ appData: state.Snippets });
+        initializing = false;
       }
-      unsubscribe();
+    } else if (state.Snippets.initialized === false && snippets.initialized) {
+      snippets.uninit();
     }
   });
+
   // These values are returned for testing purposes
   return snippets;
 }
 
 module.exports = {
   addSnippetsSubscriber,
   SnippetsMap,
   SnippetsProvider,
   SNIPPETS_UPDATE_INTERVAL_MS
 };
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)))
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
 
 /***/ })
 /******/ ]);
\ No newline at end of file
--- a/browser/extensions/activity-stream/data/content/activity-stream.css
+++ b/browser/extensions/activity-stream/data/content/activity-stream.css
@@ -59,17 +59,19 @@ input {
     background-image: url("assets/glyph-unpin-16.svg"); }
   .icon.icon-edit {
     background-image: url("assets/glyph-edit-16.svg"); }
   .icon.icon-pocket {
     background-image: url("assets/glyph-pocket-16.svg"); }
   .icon.icon-historyItem {
     background-image: url("assets/glyph-historyItem-16.svg"); }
   .icon.icon-trending {
-    background-image: url("assets/glyph-trending-16.svg"); }
+    background-image: url("assets/glyph-trending-16.svg");
+    transform: translateY(2px);
+    /* trending bolt is visually top heavy */ }
   .icon.icon-now {
     background-image: url("chrome://browser/skin/history.svg"); }
   .icon.icon-topsites {
     background-image: url("assets/glyph-topsites-16.svg"); }
   .icon.icon-pin-small {
     background-image: url("assets/glyph-pin-12.svg");
     background-size: 12px;
     height: 12px;
@@ -312,27 +314,36 @@ main {
       border-radius: 6px;
       box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
       background-size: cover;
       background-position: top left;
       transition: opacity 1s;
       opacity: 0; }
       .top-sites-list .top-site-outer .screenshot.active {
         opacity: 1; }
-    .top-sites-list .top-site-outer .tippy-top-icon {
+    .top-sites-list .top-site-outer .top-site-icon {
       position: absolute;
-      top: 0;
-      left: 0;
-      height: 100%;
-      width: 100%;
       border-radius: 6px;
       box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
       background-position: center center;
-      background-size: 96px;
-      background-repeat: no-repeat; }
+      background-repeat: no-repeat;
+      background-color: #F9F9FA; }
+    .top-sites-list .top-site-outer .rich-icon {
+      top: 0;
+      offset-inline-start: 0;
+      height: 100%;
+      width: 100%;
+      background-size: 96px; }
+    .top-sites-list .top-site-outer .default-icon {
+      z-index: 1;
+      top: -6px;
+      offset-inline-start: -6px;
+      height: 42px;
+      width: 42px;
+      background-size: 32px; }
     .top-sites-list .top-site-outer .title {
       font: message-box;
       height: 30px;
       line-height: 30px;
       text-align: center;
       width: 96px;
       position: relative; }
       .top-sites-list .top-site-outer .title .icon {
@@ -1012,21 +1023,20 @@ main {
     bottom: 0;
     left: 0;
     right: 0;
     color: #737373;
     font-size: 11px;
     display: flex; }
   .card-outer .card-context-icon {
     fill: rgba(12, 12, 13, 0.6);
-    font-size: 13px;
-    margin-inline-end: 6px;
-    display: block; }
+    margin-inline-end: 6px; }
   .card-outer .card-context-label {
     flex-grow: 1;
+    line-height: 16px;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap; }
 
 .manual-migration-container {
   color: #4A4A4F;
   font-size: 13px;
   line-height: 15px;
--- a/browser/extensions/activity-stream/data/content/activity-stream.html
+++ b/browser/extensions/activity-stream/data/content/activity-stream.html
@@ -1,10 +1,10 @@
 <!doctype html>
-<html lang="en-us" dir="ltr">
+<html lang="" dir="ltr">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy-Report-Only" content="script-src 'unsafe-inline'; img-src http: https: data: blob:; style-src 'unsafe-inline'; child-src 'none'; object-src 'none'; report-uri https://tiles.services.mozilla.com/v4/links/activity-stream/csp">
     <title></title>
     <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/data/content/activity-stream.css" />
   </head>
--- a/browser/extensions/activity-stream/data/locales.json
+++ b/browser/extensions/activity-stream/data/locales.json
@@ -227,19 +227,21 @@
     "settings_pane_topsites_header": "Qabaqcıl Saytlar",
     "settings_pane_topsites_body": "Ən çox ziyarət etdiyiniz saytları görün.",
     "settings_pane_topsites_options_showmore": "İki sətir göstər",
     "settings_pane_bookmarks_header": "Son Əlfəcinlər",
     "settings_pane_bookmarks_body": "Yeni yaradılan əlfəcinlər tək bir əlverişli yerdə.",
     "settings_pane_visit_again_header": "Təkrar ziyarət et",
     "settings_pane_visit_again_body": "Firefox tarixçənizdən yadda saxlamaq və ya geri qayıtmaq istəyə biləcəyiniz hissələri göstərəcək.",
     "settings_pane_highlights_header": "Seçilmişlər",
+    "settings_pane_highlights_body2": "Son ziyarət etdiyiniz və ya əlfəcinlədiyiniz maraqlı məzmunlara rahat qayıdın.",
     "settings_pane_highlights_options_bookmarks": "Əlfəcinlər",
     "settings_pane_highlights_options_visited": "Baxılmış Saytlar",
     "settings_pane_snippets_header": "Hissələr",
+    "settings_pane_snippets_body": "Mozilladan Firefox, internet mədəniyyəti və digər yeniliklər haqqında qısa bildirişlər oxuyun.",
     "settings_pane_done_button": "Oldu",
     "edit_topsites_button_text": "Redaktə et",
     "edit_topsites_button_label": "Qabaqcıl Saytlar bölümünüzü fərdiləşdirin",
     "edit_topsites_showmore_button": "Daha çox göstər",
     "edit_topsites_showless_button": "Daha az göstər",
     "edit_topsites_done_button": "Oldu",
     "edit_topsites_pin_button": "Bu saytı sabitlə",
     "edit_topsites_unpin_button": "Bu saytı çıxart",
@@ -252,16 +254,18 @@
     "topsites_form_url_placeholder": "Ünvanı yazın və ya yapışdırın",
     "topsites_form_add_button": "Əlavə et",
     "topsites_form_save_button": "Saxla",
     "topsites_form_cancel_button": "Ləğv et",
     "topsites_form_url_validation": "Doğru ünvan tələb olunur",
     "pocket_read_more": "Məşhur Mövzular:",
     "pocket_read_even_more": "Daha çox hekayə gör",
     "pocket_feedback_header": "25 milyon nəfərin dəstəyi ilə internetin ən yaxşıları.",
+    "pocket_description": "Mozilla ailəsinin yeni üzvü olan Pocket ilə yüksək keyfiyyətli məzmunları kəşf edin.",
+    "highlights_empty_state": "İnternetdə gəzməyə başlayın, burada ziyarət edəcəyiniz və ya əlfəcinləyəcəyiniz məqalə, video və digər səhifələri göstərəcəyik.",
     "topstories_empty_state": "Hamısını oxudunuz. Yeni {provider} məqalələri üçün daha sonra təkrar yoxlayın. Gözləyə bilmirsiz? Məşhur mövzu seçərək internetdən daha çox gözəl məqalələr tapın.",
     "manual_migration_explanation2": "Firefox səyyahını digər səyyahlardan olan əlfəcin, tarixçə və parollar ilə yoxlayın.",
     "manual_migration_cancel_button": "Xeyr, Təşəkkürlər",
     "manual_migration_import_button": "İndi idxal et"
   },
   "be": {
     "newtab_page_title": "Новая картка",
     "default_label_loading": "Загрузка…",
@@ -409,19 +413,21 @@
     "settings_pane_topsites_header": "Най-посещавани",
     "settings_pane_topsites_body": "Достъп до сайтовете, които посещавате най-често.",
     "settings_pane_topsites_options_showmore": "Показване на два реда",
     "settings_pane_bookmarks_header": "Последни отметки",
     "settings_pane_bookmarks_body": "Всички нови отметки на едно място.",
     "settings_pane_visit_again_header": "Посещаване",
     "settings_pane_visit_again_body": "Firefox ще ви показва части от вашата история на разглеждане, към която бихте желали да се върнете или запомните.",
     "settings_pane_highlights_header": "Акценти",
+    "settings_pane_highlights_body2": "Намерете интересните неща, които скоро сте посетили или отметнали.",
     "settings_pane_highlights_options_bookmarks": "Отметки",
     "settings_pane_highlights_options_visited": "Посетени страници",
     "settings_pane_snippets_header": "Изрезки",
+    "settings_pane_snippets_body": "Четете кратки и радостни новини от Mozilla относно Firefox, интернет-културата и случайни мемета.",
     "settings_pane_done_button": "Готово",
     "edit_topsites_button_text": "Редактиране",
     "edit_topsites_button_label": "Настройки на най-посещаваните",
     "edit_topsites_showmore_button": "Повече",
     "edit_topsites_showless_button": "По-малко",
     "edit_topsites_done_button": "Готово",
     "edit_topsites_pin_button": "Закачане",
     "edit_topsites_unpin_button": "Премахване от закачените",
@@ -435,16 +441,17 @@
     "topsites_form_add_button": "Добавяне",
     "topsites_form_save_button": "Запазване",
     "topsites_form_cancel_button": "Отказ",
     "topsites_form_url_validation": "Необходим е валиден URL",
     "pocket_read_more": "Популярни теми:",
     "pocket_read_even_more": "Повече статии",
     "pocket_feedback_header": "Най-доброто от интернет, подбрано от над 25 милиона души.",
     "pocket_description": "Открийте висококачествено съдържание, което иначе може да пропуснете, с помощта на Pocket, вече част от Mozilla.",
+    "highlights_empty_state": "Разглеждайте и тук ще ви покажем някои от най-добрите статии, видео и други страници, които сте посетили или отметнали наскоро.",
     "topstories_empty_state": "Разгледахте всичко. Проверете по-късно за повече истории от {provider}. Нямате търпение? Изберете популярна тема, за да откриете повече истории из цялата Мрежа.",
     "manual_migration_explanation2": "Опитайте Firefox с отметките, историята и паролите от друг четец.",
     "manual_migration_cancel_button": "Не, благодаря",
     "manual_migration_import_button": "Внасяне"
   },
   "bn-BD": {
     "newtab_page_title": "নতুন ট্যাব",
     "default_label_loading": "লোড হচ্ছে…",
@@ -1050,20 +1057,20 @@
     "type_label_topic": "Thema",
     "type_label_now": "Jetzt",
     "menu_action_bookmark": "Lesezeichen",
     "menu_action_remove_bookmark": "Lesezeichen entfernen",
     "menu_action_copy_address": "Adresse kopieren",
     "menu_action_email_link": "Link per E-Mail versenden…",
     "menu_action_open_new_window": "In neuem Fenster öffnen",
     "menu_action_open_private_window": "In neuem privaten Fenster öffnen",
-    "menu_action_dismiss": "Schließen",
+    "menu_action_dismiss": "Entfernen",
     "menu_action_delete": "Aus Chronik löschen",
     "menu_action_pin": "Anheften",
-    "menu_action_unpin": "Lösen",
+    "menu_action_unpin": "Ablösen",
     "confirm_history_delete_p1": "Soll wirklich jede Instanz dieser Seite aus Ihrer Chronik gelöscht werden?",
     "confirm_history_delete_notice_p2": "Diese Aktion kann nicht rückgängig gemacht werden.",
     "menu_action_save_to_pocket": "Bei Pocket speichern",
     "search_for_something_with": "Nach {search_term} suchen mit:",
     "search_button": "Suchen",
     "search_header": "{search_engine_name}-Suche",
     "search_web_placeholder": "Das Web durchsuchen",
     "search_settings": "Sucheinstellungen ändern",
@@ -1072,18 +1079,18 @@
     "section_info_privacy_notice": "Datenschutzhinweis",
     "welcome_title": "Willkommen im neuen Tab",
     "welcome_body": "Firefox nutzt diesen Bereich, um Ihnen Ihre wichtigsten Lesezeichen, Artikel, Videos und kürzlich besuchten Seiten anzuzeigen, damit Sie diese einfach wiederfinden.",
     "welcome_label": "Auswahl Ihrer wichtigsten Seiten",
     "time_label_less_than_minute": "< 1 min",
     "time_label_minute": "{number} m",
     "time_label_hour": "{number} h",
     "time_label_day": "{number} t",
-    "settings_pane_button_label": "Neuer-Tab-Seite anpassen",
-    "settings_pane_header": "Einstellungen zum neuen Tab",
+    "settings_pane_button_label": "Einstellungen für neue Tabs anpassen",
+    "settings_pane_header": "Einstellungen für neue Tabs",
     "settings_pane_body2": "Wählen Sie aus, was auf dieser Seite angezeigt wird.",
     "settings_pane_search_header": "Suche",
     "settings_pane_search_body": "Suchen Sie aus einem neuen Tab im Internet.",
     "settings_pane_topsites_header": "Meistbesuchte Seiten",
     "settings_pane_topsites_body": "Schneller Zugriff auf Ihre meistbesuchten Websites.",
     "settings_pane_topsites_options_showmore": "Zwei Reihen anzeigen",
     "settings_pane_bookmarks_header": "Neue Lesezeichen",
     "settings_pane_bookmarks_body": "Ihre neu erstellten Lesezeichen praktisch an einem Ort.",
@@ -1103,28 +1110,28 @@
     "edit_topsites_done_button": "Fertig",
     "edit_topsites_pin_button": "Website immer in aktueller Position anzeigen",
     "edit_topsites_unpin_button": "Diese Website lösen",
     "edit_topsites_edit_button": "Diese Website bearbeiten",
     "edit_topsites_dismiss_button": "Website entfernen",
     "edit_topsites_add_button": "Hinzufügen",
     "topsites_form_add_header": "Neue meistbesuchte Seite",
     "topsites_form_edit_header": "Meistbesuchte Seite bearbeiten",
-    "topsites_form_title_placeholder": "Titel eingeben",
-    "topsites_form_url_placeholder": "Eine URL eingeben oder einfügen",
+    "topsites_form_title_placeholder": "Name eingeben",
+    "topsites_form_url_placeholder": "Eine Adresse eingeben oder einfügen",
     "topsites_form_add_button": "Hinzufügen",
     "topsites_form_save_button": "Speichern",
     "topsites_form_cancel_button": "Abbrechen",
     "topsites_form_url_validation": "Gültige URL erforderlich",
     "pocket_read_more": "Beliebte Themen:",
     "pocket_read_even_more": "Weitere Nachrichten ansehen",
     "pocket_feedback_header": "Das Beste aus dem Web, zusammengetragen von 25 Millionen Menschen.",
     "pocket_description": "Entdecken Sie qualitativ hochwertige Inhalte mithilfe von Pocket (jetzt Teil von von Mozilla), die Sie ansonsten verpassen würden.",
     "highlights_empty_state": "Surfen Sie los und wir zeigen Ihnen hier tolle Artikel, Videos und andere Seiten, die Sie kürzlich besucht oder als Lesezeichen gespeichert haben.",
-    "topstories_empty_state": "Jetzt kennen Sie die Neuigkeiten. Schauen Sie später wieder vorbei, um neue Informationen von {provider} zu erhalten. Können sie nicht warten? Wählen Sie ein beliebtes Thema und lesen Sie weitere interessante Geschichten aus dem Internet.",
+    "topstories_empty_state": "Jetzt kennen Sie die Neuigkeiten. Schauen Sie später wieder vorbei, um neue Informationen von {provider} zu erhalten. Können Sie nicht warten? Wählen Sie ein beliebtes Thema und lesen Sie weitere interessante Geschichten aus dem Internet.",
     "manual_migration_explanation2": "Probieren Sie Firefox aus und importieren Sie die Lesezeichen, Chronik und Passwörter eines anderen Browsers.",
     "manual_migration_cancel_button": "Nein, danke",
     "manual_migration_import_button": "Jetzt importieren"
   },
   "dsb": {
     "newtab_page_title": "Nowy rejtark",
     "default_label_loading": "Zacytujo se…",
     "header_top_sites": "Nejcesćej woglědane sedła",
@@ -1758,16 +1765,17 @@
     "manual_migration_cancel_button": "No, gracias",
     "manual_migration_import_button": "Importar ahora"
   },
   "es-ES": {
     "newtab_page_title": "Nueva pestaña",
     "default_label_loading": "Cargando…",
     "header_top_sites": "Sitios favoritos",
     "header_stories": "Historias populares",
+    "header_highlights": "Destacados",
     "header_visit_again": "Visitar de nuevo",
     "header_bookmarks": "Marcadores recientes",
     "header_recommended_by": "Recomendado por {provider}",
     "header_bookmarks_placeholder": "Todavía no tienes ningún marcador.",
     "header_stories_from": "desde",
     "type_label_visited": "Visitados",
     "type_label_bookmarked": "En marcadores",
     "type_label_synced": "Sincronizado desde otro dispositivo",
@@ -1789,37 +1797,43 @@
     "confirm_history_delete_notice_p2": "Esta acción no se puede deshacer.",
     "menu_action_save_to_pocket": "Guardar en Pocket",
     "search_for_something_with": "Buscar {search_term} con:",
     "search_button": "Buscar",
     "search_header": "Búsqueda de {search_engine_name}",
     "search_web_placeholder": "Buscar en la Web",
     "search_settings": "Cambiar ajustes de búsqueda",
     "section_info_option": "Info",
+    "section_info_send_feedback": "Enviar comentario",
+    "section_info_privacy_notice": "Aviso de privacidad",
     "welcome_title": "Bienvenido a la nueva pestaña",
     "welcome_body": "Firefox utilizará este espacio para mostrarte los marcadores, artículos y vídeos más relevantes y las páginas que has visitado recientemente, para que puedas acceder más rápido.",
     "welcome_label": "Identificar lo más destacado para ti",
     "time_label_less_than_minute": "<1m",
     "time_label_minute": "{number}m",
     "time_label_hour": "{number}h",
     "time_label_day": "{number}d",
     "settings_pane_button_label": "Personalizar la página Nueva pestaña",
     "settings_pane_header": "Preferencias de nueva pestaña",
-    "settings_pane_body": "Elige qué quieres ver al abrir una nueva pestaña",
+    "settings_pane_body2": "Elige lo quieras ver en esta página.",
     "settings_pane_search_header": "Buscar",
     "settings_pane_search_body": "Busca en la Web desde tu nueva pestaña.",
     "settings_pane_topsites_header": "Sitios populares",
     "settings_pane_topsites_body": "Accede a las páginas que más visitas.",
     "settings_pane_topsites_options_showmore": "Mostrar dos líneas",
     "settings_pane_bookmarks_header": "Marcadores recientes",
     "settings_pane_bookmarks_body": "Tus marcadores recién creados, fácilmente accesibles.",
     "settings_pane_visit_again_header": "Visitar de nuevo",
     "settings_pane_visit_again_body": "Firefox te mostrará partes de tu historial de navegación que te gustaría recordar o volver a visitar.",
-    "settings_pane_pocketstories_header": "Historias populares",
-    "settings_pane_pocketstories_body": "Pocket, que forma parte de la familia de Mozilla, te ayudará a encontrar contenido de alta calidad que puede que no encuentres de otra forma.",
+    "settings_pane_highlights_header": "Destacados",
+    "settings_pane_highlights_body2": "Vuelve a encontrar todas las cosas interesantes que hayas visitado o marcado recientemente.",
+    "settings_pane_highlights_options_bookmarks": "Marcadores",
+    "settings_pane_highlights_options_visited": "Sitios visitados",
+    "settings_pane_snippets_header": "Fragmentos de código",
+    "settings_pane_snippets_body": "Lee actualizaciones breves de Mozilla sobre Firefox, la cultura de internet y el típico meme aleatorio.",
     "settings_pane_done_button": "Hecho",
     "edit_topsites_button_text": "Editar",
     "edit_topsites_button_label": "Personalizar la sección de Sitios populares",
     "edit_topsites_showmore_button": "Mostrar más",
     "edit_topsites_showless_button": "Mostrar menos",
     "edit_topsites_done_button": "Hecho",
     "edit_topsites_pin_button": "Fijar este sitio",
     "edit_topsites_unpin_button": "Eliminar este sitio fijo",
@@ -1832,20 +1846,20 @@
     "topsites_form_url_placeholder": "Escribir o pegar una URL",
     "topsites_form_add_button": "Agregar",
     "topsites_form_save_button": "Guardar",
     "topsites_form_cancel_button": "Cancelar",
     "topsites_form_url_validation": "Se requiere una URL válida",
     "pocket_read_more": "Temas populares:",
     "pocket_read_even_more": "Ver más historias",
     "pocket_feedback_header": "Lo mejor de la web, confirmado por más de 25 millones de personas.",
-    "pocket_feedback_body": "Pocket, que forma parte de la familia de Mozilla, te ayudará a encontrar contenido de alta calidad que puede que no encuentres de otra forma.",
-    "pocket_send_feedback": "Enviar comentario",
+    "pocket_description": "Gracias a Pocket, que ahora forma parte de Mozilla, podrás descubrir contenido de alta calidad que de otra forma te perderías.",
+    "highlights_empty_state": "Empieza a navegar y nosotros te mostraremos aquí algunos de los mejores artículos, videos y otras páginas que hayas visitado recientemente o agregado a marcadores.",
     "topstories_empty_state": "Ya estás al día. Vuelve luego y busca más historias de {provider}. ¿No puedes esperar? Selecciona un tema popular y encontrás más historias alucinantes por toda la web.",
-    "manual_migration_explanation": "Prueba Firefox con tusmarcadores y sitios favoritos importados desde otro navegador.",
+    "manual_migration_explanation2": "Prueba Firefox con los marcadores, historial y contraseñas de otro navegador.",
     "manual_migration_cancel_button": "No, gracias",
     "manual_migration_import_button": "Importar ahora"
   },
   "es-MX": {
     "newtab_page_title": "Nueva pestaña",
     "default_label_loading": "Cargando…",
     "header_top_sites": "Sitios favoritos",
     "header_stories": "Historias populares",
@@ -2358,17 +2372,17 @@
     "settings_pane_topsites_header": "Sites les plus visités",
     "settings_pane_topsites_body": "Accédez aux sites que vous consultez le plus.",
     "settings_pane_topsites_options_showmore": "Afficher deux lignes",
     "settings_pane_bookmarks_header": "Marque-pages récents",
     "settings_pane_bookmarks_body": "Vos nouveaux marque-pages, facilement accessibles.",
     "settings_pane_visit_again_header": "Visiter à nouveau",
     "settings_pane_visit_again_body": "Firefox affichera des extraits de votre historique de navigation dont vous pourriez vouloir vous souvenir ou que vous pourriez vouloir revisiter.",
     "settings_pane_highlights_header": "Éléments-clés",
-    "settings_pane_highlights_body2": "Retrouvez des pages inintéressantes que vous avez déjà visitées récemment ou ajoutées aux marque-pages.",
+    "settings_pane_highlights_body2": "Retrouvez des pages intéressantes que vous avez visitées récemment ou ajoutées aux marque-pages.",
     "settings_pane_highlights_options_bookmarks": "Marque-pages",
     "settings_pane_highlights_options_visited": "Sites visités",
     "settings_pane_snippets_header": "Brèves",
     "settings_pane_snippets_body": "Consultez les brèves de Mozilla à propos de Firefox, la culture Internet, mais aussi quelques mèmes Internet de temps en temps.",
     "settings_pane_done_button": "Terminé",
     "edit_topsites_button_text": "Modifier",
     "edit_topsites_button_label": "Personnaliser la section Sites les plus visités",
     "edit_topsites_showmore_button": "Afficher plus",
@@ -3002,16 +3016,17 @@
     "manual_migration_cancel_button": "Ně, dźakuju so",
     "manual_migration_import_button": "Nětko importować"
   },
   "hu": {
     "newtab_page_title": "Új lap",
     "default_label_loading": "Betöltés…",
     "header_top_sites": "Népszerű oldalak",
     "header_stories": "Népszerű történetek",
+    "header_highlights": "Kiemelések",
     "header_visit_again": "Látogasson el ismét",
     "header_bookmarks": "Friss könyvjelzők",
     "header_recommended_by": "A(z) {provider} ajánlásával",
     "header_bookmarks_placeholder": "Még nincs könyvjelzője.",
     "header_stories_from": "innen:",
     "type_label_visited": "Látogatott",
     "type_label_bookmarked": "Könyvjelzőzött",
     "type_label_synced": "Másik eszközről szinkronizálva",
@@ -3033,37 +3048,43 @@
     "confirm_history_delete_notice_p2": "Ez a művelet nem vonható vissza.",
     "menu_action_save_to_pocket": "Mentés a Pocketbe",
     "search_for_something_with": "„{search_term}” keresése ezzel:",
     "search_button": "Keresés",
     "search_header": "{search_engine_name} keresés",
     "search_web_placeholder": "Keresés a weben",
     "search_settings": "Keresési beállítások módosítása",
     "section_info_option": "Információ",
+    "section_info_send_feedback": "Visszajelzés küldése",
+    "section_info_privacy_notice": "Adatvédelmi nyilatkozat",
     "welcome_title": "Üdvözöljük az új lapon",
     "welcome_body": "A Firefox ezt a területet a leginkább releváns könyvjelzők, cikkek, videók és nemrég látogatott oldalak megjelenítésére fogja használni, így könnyedén visszatalálhat hozzájuk.",
     "welcome_label": "A kiemeléseinek azonosítása",
     "time_label_less_than_minute": "<1 p",
     "time_label_minute": "{number} p",
     "time_label_hour": "{number} ó",
     "time_label_day": "{number} n",
     "settings_pane_button_label": "Az Új lap oldal személyre szabása",
     "settings_pane_header": "Új lap beállításai",
-    "settings_pane_body": "Válassza ki, hogy mit lát, amikor megnyit egy új lapot.",
+    "settings_pane_body2": "Válassza ki, hogy mit akar látni ezen az oldalon.",
     "settings_pane_search_header": "Keresés",
     "settings_pane_search_body": "Keresés a weben az új lapon.",
     "settings_pane_topsites_header": "Népszerű oldalak",
     "settings_pane_topsites_body": "A leggyakrabban látogatott webhelyek elérése.",
     "settings_pane_topsites_options_showmore": "Két sor megjelenítése",
     "settings_pane_bookmarks_header": "Friss könyvjelzők",
     "settings_pane_bookmarks_body": "A frissen létrehozott könyvjelzői egy praktikus helyen.",
     "settings_pane_visit_again_header": "Látogasson el ismét",
     "settings_pane_visit_again_body": "A Firefox megjeleníti a böngészési előzményeinek azt a részét, amelyet lehet hogy meg szeretne jegyezni, vagy ahová vissza akar térni.",
-    "settings_pane_pocketstories_header": "Népszerű történetek",
-    "settings_pane_pocketstories_body": "A Pocket a Mozilla család tagja, segít az olyan jó minőségű tartalmak fellelésében, melyekkel egyébként nem is találkozott volna.",
+    "settings_pane_highlights_header": "Kiemelések",
+    "settings_pane_highlights_body2": "Találjon vissza azokhoz az érdekes dolgokhoz, amelyeket meglátogatott vagy könyvjelzőzött.",
+    "settings_pane_highlights_options_bookmarks": "Könyvjelzők",
+    "settings_pane_highlights_options_visited": "Látogatott helyek",
+    "settings_pane_snippets_header": "Töredékek",
+    "settings_pane_snippets_body": "Olvasson rövid és érdekes híreket a Mozillától, a Firefoxról, az internetes kultúráról, és időnként kapjon mémeket.",
     "settings_pane_done_button": "Kész",
     "edit_topsites_button_text": "Szerkesztés",
     "edit_topsites_button_label": "A Népszerű oldalak rész testreszabása",
     "edit_topsites_showmore_button": "Több megjelenítése",
     "edit_topsites_showless_button": "Kevesebb megjelenítése",
     "edit_topsites_done_button": "Kész",
     "edit_topsites_pin_button": "Webhely rögzítése",
     "edit_topsites_unpin_button": "Rögzítés feloldása",
@@ -3076,20 +3097,20 @@
     "topsites_form_url_placeholder": "Írjon vagy illesszen be egy URL-t",
     "topsites_form_add_button": "Hozzáadás",
     "topsites_form_save_button": "Mentés",
     "topsites_form_cancel_button": "Mégse",
     "topsites_form_url_validation": "Érvényes URL szükséges",
     "pocket_read_more": "Népszerű témák:",
     "pocket_read_even_more": "További történetek",
     "pocket_feedback_header": "A web legjava, több mint 25 millió ember válogatásában.",
-    "pocket_feedback_body": "A Pocket a Mozilla család tagja, segít az olyan jó minőségű tartalmak fellelésében, melyekkel egyébként nem is találkozott volna.",
-    "pocket_send_feedback": "Visszajelzés küldése",
+    "pocket_description": "Fedezzen fel olyan, magas minőségű tartalmakat, amelyek egyébként elkerülnék a figyelmét, a Pocket segítségével, amely most már a Mozilla része.",
+    "highlights_empty_state": "Kezdjen el böngészni, és itt fognak megjelenni azok a nagyszerű cikkek, videók és más lapok, amelyeket nemrég meglátogatott vagy könyvjelzőzött.",
     "topstories_empty_state": "Már felzárkózott. Nézzen vissza később a legújabb {provider} hírekért. Nem tud várni? Válasszon egy népszerű témát, hogy még több sztorit találjon a weben.",
-    "manual_migration_explanation": "Próbálja ki a Firefoxot egy másik böngészőben lévő kedvenc oldalaival és könyvjelzőivel.",
+    "manual_migration_explanation2": "Próbálja ki a Firefoxot másik böngészőből származó könyvjelzőkkel, előzményekkel és jelszavakkal.",
     "manual_migration_cancel_button": "Köszönöm, nem",
     "manual_migration_import_button": "Importálás most"
   },
   "hy-AM": {
     "newtab_page_title": "Նոր ներդիր",
     "default_label_loading": "Բեռնվում է...",
     "header_top_sites": "Լավագույն կայքեր",
     "header_highlights": "Գունանշում",
@@ -3118,16 +3139,17 @@
     "time_label_hour": "{number} ժ",
     "time_label_day": "{number} օր"
   },
   "id": {
     "newtab_page_title": "Tab Baru",
     "default_label_loading": "Memuat…",
     "header_top_sites": "Situs Teratas",
     "header_stories": "Cerita Utama",
+    "header_highlights": "Sorotan",
     "header_visit_again": "Kunjungi Lagi",
     "header_bookmarks": "Markah Terbaru",
     "header_recommended_by": "Disarankan oleh {provider}",
     "header_bookmarks_placeholder": "Anda belum memiliki markah.",
     "header_stories_from": "dari",
     "type_label_visited": "Dikunjungi",
     "type_label_bookmarked": "Dimarkahi",
     "type_label_synced": "Disinkronkan dari perangkat lain",
@@ -3149,37 +3171,43 @@
     "confirm_history_delete_notice_p2": "Tindakan ini tidak bisa diurungkan.",
     "menu_action_save_to_pocket": "Simpan ke Pocket",
     "search_for_something_with": "Cari {search_term} lewat:",
     "search_button": "Cari",
     "search_header": "Pencarian {search_engine_name}",
     "search_web_placeholder": "Cari di Web",
     "search_settings": "Ubah Pengaturan Pencarian",
     "section_info_option": "Info",
+    "section_info_send_feedback": "Kirim Umpan Balik",
+    "section_info_privacy_notice": "Kebijakan Privasi",
     "welcome_title": "Selamat datang di tab baru",
     "welcome_body": "Firefox akan menggunakan ruang ini untuk menampilkan markah, artikel, video, dan laman yang baru-baru ini dikunjungi, yang paling relevan agar Anda bisa kembali mengunjunginya dengan mudah.",
     "welcome_label": "Mengidentifikasi Sorotan Anda",
     "time_label_less_than_minute": "<1 mnt",
     "time_label_minute": "{number} mnt",
     "time_label_hour": "{number} jam",
     "time_label_day": "{number} hr",
     "settings_pane_button_label": "Ubahsuai laman Tab Baru Anda",
     "settings_pane_header": "Preferensi Tab Baru",
-    "settings_pane_body": "Pilih apa yang Anda lihat ketika Anda membuka tab baru.",
+    "settings_pane_body2": "Pilih apa yang Anda lihat di halaman ini.",
     "settings_pane_search_header": "Pencarian",
     "settings_pane_search_body": "Cari Web dari tab baru Anda.",
     "settings_pane_topsites_header": "Situs Teratas",
     "settings_pane_topsites_body": "Mengakses situs web yang paling sering Anda kunjungi.",
     "settings_pane_topsites_options_showmore": "Tampilkan dua baris",
     "settings_pane_bookmarks_header": "Markah Terbaru",
     "settings_pane_bookmarks_body": "Markah Anda dibuat di lokasi yang praktis.",
     "settings_pane_visit_again_header": "Kunjungi Lagi",
     "settings_pane_visit_again_body": "Firefox akan menunjukkan bagian dari riwayat penjelajahan yang mungkin ingin Anda ingat atau kunjungi lagi.",
-    "settings_pane_pocketstories_header": "Cerita Utama",
-    "settings_pane_pocketstories_body": "Pocket, bagian dari keluarga Mozilla, akan membantu hubungkan Anda dengan konten berkualitas tinggi yang tak dapat Anda temukan di tempat lain.",
+    "settings_pane_highlights_header": "Sorotan",
+    "settings_pane_highlights_body2": "Temukan jalan kembali ke hal menarik yang baru saja Anda kunjungi atau dimarkah.",
+    "settings_pane_highlights_options_bookmarks": "Markah",
+    "settings_pane_highlights_options_visited": "Situs Terkunjungi",
+    "settings_pane_snippets_header": "Catatan Kecil",
+    "settings_pane_snippets_body": "Baca info pendek terbaru dari Mozilla tentang Firefox, budaya internet dan beberapa meme acak.",
     "settings_pane_done_button": "Selesai",
     "edit_topsites_button_text": "Sunting",
     "edit_topsites_button_label": "Ubahsuai bagian Situs Teratas Anda",
     "edit_topsites_showmore_button": "Tampilkan lainnya",
     "edit_topsites_showless_button": "Tampilkan lebih sedikit",
     "edit_topsites_done_button": "Selesai",
     "edit_topsites_pin_button": "Sematkan situs ini",
     "edit_topsites_unpin_button": "Lepaskan situs ini",
@@ -3192,20 +3220,20 @@
     "topsites_form_url_placeholder": "Ketik atau tempel URL",
     "topsites_form_add_button": "Tambah",
     "topsites_form_save_button": "Simpan",
     "topsites_form_cancel_button": "Batalkan",
     "topsites_form_url_validation": "URL valid diperlukan",
     "pocket_read_more": "Topik Populer:",
     "pocket_read_even_more": "Lihat Cerita Lainnya",
     "pocket_feedback_header": "Yang terbaik dari Web, dikurasi lebih dari 25 juta orang.",
-    "pocket_feedback_body": "Pocket, bagian dari keluarga Mozilla, akan membantu hubungkan Anda dengan konten berkualitas tinggi yang tak dapat Anda temukan di tempat lain.",
-    "pocket_send_feedback": "Kirim Umpanbalik",
+    "pocket_description": "Temukan konten berkualitas tinggi yang mungkin Anda lewatkan dengan bantuan Pocket, yang sekarang menjadi bagian dari Mozilla.",
+    "highlights_empty_state": "Mulai menjelajah, dan kami akan menampilkan beberapa artikel bagus, video, dan halaman lain yang baru saja Anda kunjungi atau termarkah di sini.",
     "topstories_empty_state": "Maaf Anda tercegat. Periksa lagi nanti untuk lebih banyak cerita terbaik dari {provider}. Tidak mau menunggu? Pilih topik populer untuk menemukan lebih banyak cerita hebat dari seluruh web.",
-    "manual_migration_explanation": "Cobalah Firefox dengan situs dan markah kesukaan Anda dari peramban yang lain.",
+    "manual_migration_explanation2": "Coba Firefox dengan markah, riwayat, dan sandi dari peramban lain.",
     "manual_migration_cancel_button": "Tidak, Terima kasih",
     "manual_migration_import_button": "Impor Sekarang"
   },
   "it": {
     "newtab_page_title": "Nuova scheda",
     "default_label_loading": "Caricamento…",
     "header_top_sites": "Siti principali",
     "header_stories": "Storie principali",
@@ -3483,16 +3511,17 @@
     "manual_migration_cancel_button": "არა, გმადლობთ",
     "manual_migration_import_button": "ახლავე გადმოტანა"
   },
   "kab": {
     "newtab_page_title": "Iccer amaynut",
     "default_label_loading": "Asali…",
     "header_top_sites": "Ismal ifazen",
     "header_stories": "Tiqsiɣin ifazen",
+    "header_highlights": "Asebrureq",
     "header_visit_again": "Rzu tikelt-nniḍen",
     "header_bookmarks": "Ticraḍ n melmi kan",
     "header_recommended_by": "Iwelleh-it-id {provider}",
     "header_bookmarks_placeholder": "Ur ɣur-k ara ticraḍ yakan.",
     "header_stories_from": "seg",
     "type_label_visited": "Yettwarza",
     "type_label_bookmarked": "Yettwacreḍ",
     "type_label_synced": "Yemtawi seg ibenk-nniḍen",
@@ -3514,37 +3543,43 @@
     "confirm_history_delete_notice_p2": "Tigawt-agi ur tettuɣal ara ar deffir.",
     "menu_action_save_to_pocket": "Sekles ɣer Pocket",
     "search_for_something_with": "Nadi γef {search_term} s:",
     "search_button": "Nadi",
     "search_header": "Anadi {search_engine_name}",
     "search_web_placeholder": "Nadi di Web",
     "search_settings": "Snifel iγewwaṛen n unadi",
     "section_info_option": "Talɣut",
+    "section_info_send_feedback": "Azen tikti",
+    "section_info_privacy_notice": "Tasertit n tbaḍnit",
     "welcome_title": "Ansuf ar yiccer amaynut",
     "welcome_body": "Firefox ad iseqdec tallunt akken ad d-yesken akk ticraḍ n isebtar iwulmen, imagraden, tividyutin, akked isebtar aniɣer terziḍ melmi kan, ihi tzemreḍ ad d-uɣaleḍ ɣer-sen s wudem fessusen.",
     "welcome_label": "Asulu n iferdisen tisura",
     "time_label_less_than_minute": "<1 n tesdat",
     "time_label_minute": "{number} n tesdatin",
     "time_label_hour": "{number} n isragen",
     "time_label_day": "{number}n wussan",
     "settings_pane_button_label": "Sagen asebter n yiccer-ik amaynut",
     "settings_pane_header": "Ismenyifen n yiccer amaynut",
-    "settings_pane_body": "Fren ayen ara twaliḍ ticki teldiḍ iccer imaynut.",
+    "settings_pane_body2": "Fren ayen ad twaliḍ deg usebter-agi.",
     "settings_pane_search_header": "Nadi",
     "settings_pane_search_body": "Nadi di Web seg iccer-ik amaynut.",
     "settings_pane_topsites_header": "Ismal ifazen",
     "settings_pane_topsites_body": "Kcem ar yesmal web i trezzuḍ s waṭas.",
     "settings_pane_topsites_options_showmore": "Sken sin izirigen",
     "settings_pane_bookmarks_header": "Ticraḍ n melmi kan",
     "settings_pane_bookmarks_body": "Ticraḍ yettwarnan melmi kan deg iwen n umdiq ɣef afus.",
     "settings_pane_visit_again_header": "Rzu tikelt-nniḍen",
     "settings_pane_visit_again_body": "Firefox ad d-yesken tukkist n umazray-ik n tunigin i tzemreḍ ad twalid tikelt-nniḍen.",
-    "settings_pane_pocketstories_header": "Tiqsiɣin ifazen",
-    "settings_pane_pocketstories_body": "Pocket, aɛeggal n twaxult n Mozilla, ak-d-yefk afus ad twaliḍ agbur n tɣara meqqren i tzemred ad tzegleḍ.",
+    "settings_pane_highlights_header": "Asebrureq",
+    "settings_pane_highlights_body2": "Aff abrid-ik γer wayen i tḥemmleḍ i γef terziḍ yakan neγ tcerḍeḍ-t.",
+    "settings_pane_highlights_options_bookmarks": "Ticraḍ n isebtar",
+    "settings_pane_highlights_options_visited": "Ismal yettwarzan",
+    "settings_pane_snippets_header": "Tiwzillin",
+    "settings_pane_snippets_body": "Wali issalen n Mozilla γef Firefox, adlis internet, akked issalen nniṣen sya γer da.",
     "settings_pane_done_button": "Immed",
     "edit_topsites_button_text": "Ẓreg",
     "edit_topsites_button_label": "Sagen tigezmi n ismal ifazen",
     "edit_topsites_showmore_button": "Sken ugar",
     "edit_topsites_showless_button": "Sken qel",
     "edit_topsites_done_button": "Immed",
     "edit_topsites_pin_button": "Ṭṭef asmel-agi",
     "edit_topsites_unpin_button": "Serreḥ asmel-agi",
@@ -3557,20 +3592,20 @@
     "topsites_form_url_placeholder": "Aru neɣ sekcem tansa URL",
     "topsites_form_add_button": "Rnu",
     "topsites_form_save_button": "Sekles",
     "topsites_form_cancel_button": "Sefsex",
     "topsites_form_url_validation": "Tansa URL tameɣtut tettwasra",
     "pocket_read_more": "Isental ittwasnen aṭas:",
     "pocket_read_even_more": "Wali ugar n teqsiḍin",
     "pocket_feedback_header": "D amezwaru n Web, ittwafren sγur ugar 25 imelyan n imdanen.",
-    "pocket_feedback_body": "Pocket, aɛeggal n twaxult n Mozilla, ak-d-yefk afus ad twaliḍ agbur n tɣara meqqren i tzemred ad tzegleḍ.",
-    "pocket_send_feedback": "Azen tikti",
+    "pocket_description": "S lmendad n Pocket n Mozillan wali aqbur ifazen aṭas, s ttawil-a werǧin ad tzegleḍ taγawsa.",
+    "highlights_empty_state": "Bdu tuniginn sakin nekkni ad k-n-sken imagraden, tividyutin, akked isebtar nniḍen i γef terziḍ yakan neγ i tceṛḍeḍ dagi.",
     "topstories_empty_state": "Ulac wiyaḍ. Uɣal-d ticki s wugar n imagraden seg {provider}. Ur tebɣiḍ ara ad terǧuḍ? Fren asentel seg wid yettwasnen akken ad twaliḍ imagraden yelhan di Web.",
-    "manual_migration_explanation": "Ɛreḍ Firefox s ismal-ik inurifen akked ticraḍ seg iminig-nniḍen.",
+    "manual_migration_explanation2": "Σreḍ Firefox s ticṛaḍ n isebtar, amazray akked awalen uffiren sγur ilinigen nniḍen.",
     "manual_migration_cancel_button": "Ala, tanemmirt",
     "manual_migration_import_button": "Kter tura"
   },
   "kk": {
     "newtab_page_title": "Жаңа бет",
     "default_label_loading": "Жүктелуде…",
     "header_top_sites": "Топ сайттар",
     "header_stories": "Топ хикаялар",
@@ -3623,19 +3658,21 @@
     "settings_pane_topsites_header": "Топ сайттар",
     "settings_pane_topsites_body": "Көбірек қаралатын сайттарға қатынау.",
     "settings_pane_topsites_options_showmore": "Екі жолды көрсету",
     "settings_pane_bookmarks_header": "Соңғы бетбелгілер",
     "settings_pane_bookmarks_body": "Сіздің жаңадан жасалған бетбелгілер бір ыңғайлы жерде.",
     "settings_pane_visit_again_header": "Қайтадан шолу",
     "settings_pane_visit_again_body": "Firefox сізге есте сақтауды немесе қайта шолуды қалауыңыз мүмкін тарихыңыздың бөліктерін көрсетеді.",
     "settings_pane_highlights_header": "Ерекше жаңалықтар",
+    "settings_pane_highlights_body2": "Сіз жақында қараған немесе бетбелгілерге қосқан қызықты нәрселерге қайтатын жолды табыңыз.",
     "settings_pane_highlights_options_bookmarks": "Бетбелгілер",
     "settings_pane_highlights_options_visited": "Ашылған сайттар",
     "settings_pane_snippets_header": "Үзінділер",
+    "settings_pane_snippets_body": "Mozilla-дан Firefox және интернет мәдениеті туралы қысқа жаңалықтарды, және кездейсоқ мемдерді оқыңыз.",
     "settings_pane_done_button": "Дайын",
     "edit_topsites_button_text": "Түзету",
     "edit_topsites_button_label": "Топ сайттар санатын баптау",
     "edit_topsites_showmore_button": "Көбірек көрсету",
     "edit_topsites_showless_button": "Азырақ көрсету",
     "edit_topsites_done_button": "Дайын",
     "edit_topsites_pin_button": "Бұл сайтты жапсыру",
     "edit_topsites_unpin_button": "Бұл сайтты бекітуден алып тастау",
@@ -3648,17 +3685,20 @@
     "topsites_form_url_placeholder": "Сілтемені теріңіз немесе кірістіріңіз",
     "topsites_form_add_button": "Қосу",
     "topsites_form_save_button": "Сақтау",
     "topsites_form_cancel_button": "Бас тарту",
     "topsites_form_url_validation": "Жарамды сілтеме керек",
     "pocket_read_more": "Әйгілі тақырыптар:",
     "pocket_read_even_more": "Көбірек хикаяларды қарау",
     "pocket_feedback_header": "Интернеттің ең жақсысы, 25 миллион адаммен танылған.",
+    "pocket_description": "Ол болмаса, сіз жіберіп алатын мүмкіндігі бар жоғары сапалы құраманы Pocket көмегімен табыңыз, ол енді Mozilla-ның бөлігі болып табылады.",
+    "highlights_empty_state": "Шолуды бастаңыз, сіз жақында шолған немесе бетбелгілерге қосқан тамаша мақалалар, видеолар немесе басқа парақтардың кейбіреулері осында көрсетіледі.",
     "topstories_empty_state": "Дайын. {provider} ұсынған көбірек мақалаларды алу үшін кейінірек тексеріңіз. Күте алмайсыз ба? Интернеттен көбірек тамаша мақалаларды алу үшін әйгілі теманы таңдаңыз.",
+    "manual_migration_explanation2": "Firefox-ты басқа браузер бетбелгілері, тарихы және парольдерімен қолданып көріңіз.",
     "manual_migration_cancel_button": "Жоқ, рахмет",
     "manual_migration_import_button": "Қазір импорттау"
   },
   "km": {
     "newtab_page_title": "ផ្ទាំង​ថ្មី",
     "default_label_loading": "កំពុង​ផ្ទុក...",
     "header_top_sites": "វិបសាយ​លើ​គេ",
     "header_highlights": "ការ​រំលេច",
@@ -4371,16 +4411,17 @@
     "topsites_form_url_placeholder": "Skriv eller lim inn en URL",
     "topsites_form_add_button": "Legg til",
     "topsites_form_save_button": "Lagre",
     "topsites_form_cancel_button": "Avbryt",
     "topsites_form_url_validation": "Gyldig URL er nødvendig",
     "pocket_read_more": "Populære emner:",
     "pocket_read_even_more": "Vis flere saker",
     "pocket_feedback_header": "Det beste av nettet, kurert av over 25 millioner mennesker.",
+    "pocket_description": "Oppdag høykvalitetsinnhold som du ellers ville gå glipp av, ved hjelp av Pocket, som nå er en del av Mozilla.",
     "highlights_empty_state": "Begynn å surfe, og vi viser noen av de beste artiklene, videoer og andre sider du nylig har besøkt eller bokmerket her.",
     "topstories_empty_state": "Du har tatt igjen. Kom tilbake senere for flere topphistorier fra {provider}. Kan du ikke vente? Velg et populært emne for å finne flere gode artikler fra hele Internett.",
     "manual_migration_explanation2": "Prøv Firefox med bokmerkene, historikk og passord fra en annen nettleser.",
     "manual_migration_cancel_button": "Nei takk",
     "manual_migration_import_button": "Importer nå"
   },
   "ne-NP": {
     "newtab_page_title": "नयाँ ट्याब",
@@ -4986,17 +5027,17 @@
     "type_label_topic": "Tema",
     "type_label_now": "Ussa",
     "menu_action_bookmark": "Marcar sco segnapagina",
     "menu_action_remove_bookmark": "Allontanar il segnapagina",
     "menu_action_copy_address": "Copiar l'adressa",
     "menu_action_email_link": "Trametter la colliaziun per e-mail…",
     "menu_action_open_new_window": "Avrir en ina nova fanestra",
     "menu_action_open_private_window": "Avrir en ina nova fanestra privata",
-    "menu_action_dismiss": "Serrar",
+    "menu_action_dismiss": "Sbittar",
     "menu_action_delete": "Stizzar da la cronologia",
     "menu_action_pin": "Fixar",
     "menu_action_unpin": "Betg pli fixar",
     "confirm_history_delete_p1": "Vuls ti propi stizzar mintga instanza da questa pagina ord la cronologia?",
     "confirm_history_delete_notice_p2": "Questa acziun na po betg vegnir revocada.",
     "menu_action_save_to_pocket": "Memorisar en Pocket",
     "search_for_something_with": "Tschertgar {search_term} cun:",
     "search_button": "Tschertgar",
@@ -5366,31 +5407,33 @@
     "settings_pane_bookmarks_header": "Nedavni zaznamki",
     "settings_pane_bookmarks_body": "Vaši novo ustvarjeni zaznamki na enem mestu.",
     "settings_pane_visit_again_header": "Obiščite znova",
     "settings_pane_visit_again_body": "Firefox vam bo prikazoval dele zgodovine brskanja, ki bi se jih morda želeli spomniti ali se nanje vrniti.",
     "settings_pane_highlights_header": "Poudarki",
     "settings_pane_highlights_body2": "Najdite pot nazaj do zanimivih strani, ki ste jih nedavno obiskali ali dodali med zaznamke.",
     "settings_pane_highlights_options_bookmarks": "Zaznamki",
     "settings_pane_highlights_options_visited": "Obiskane strani",
+    "settings_pane_snippets_header": "Izrezki",
+    "settings_pane_snippets_body": "Spremljajte kratke novice o Mozilli in Firefoxu, kulturi interneta in si občasno oglejte kak meme.",
     "settings_pane_done_button": "Končano",
     "edit_topsites_button_text": "Uredi",
     "edit_topsites_button_label": "Prilagodite odsek Glavne strani",
     "edit_topsites_showmore_button": "Prikaži več",
     "edit_topsites_showless_button": "Prikaži manj",
     "edit_topsites_done_button": "Končano",
     "edit_topsites_pin_button": "Pripni to stran",
     "edit_topsites_unpin_button": "Odpni to stran",
     "edit_topsites_edit_button": "Uredi to stran",
     "edit_topsites_dismiss_button": "Odstrani to stran",
     "edit_topsites_add_button": "Dodaj",
     "topsites_form_add_header": "Nova glavna stran",
     "topsites_form_edit_header": "Uredi glavno stran",
-    "topsites_form_title_placeholder": "Vnesite naslov",
-    "topsites_form_url_placeholder": "Vnesite ali prilepite URL",
+    "topsites_form_title_placeholder": "Vnesite ime",
+    "topsites_form_url_placeholder": "Vnesite ali prilepite spletni naslov",
     "topsites_form_add_button": "Dodaj",
     "topsites_form_save_button": "Shrani",
     "topsites_form_cancel_button": "Prekliči",
     "topsites_form_url_validation": "Vnesite veljaven URL",
     "pocket_read_more": "Priljubljene teme:",
     "pocket_read_even_more": "Prikaži več vesti",
     "pocket_feedback_header": "Najboljše s spleta, kar je izbralo več kot 25 milijonov ljudi.",
     "pocket_description": "Odkrijte kakovostno vsebino, ki bi jo sicer spregledali, s pomočjo Pocketa (zdaj dela Mozille).",
@@ -5720,16 +5763,17 @@
     "manual_migration_cancel_button": "பரவாயில்லை",
     "manual_migration_import_button": "இப்போது இறக்கு"
   },
   "te": {
     "newtab_page_title": "కొత్త ట్యాబు",
     "default_label_loading": "వస్తోంది…",
     "header_top_sites": "మేటి సైట్లు",
     "header_stories": "ముఖ్య కథనాలు",
+    "header_highlights": "విశేషాలు",
     "header_visit_again": "మళ్లీ సందర్శించండి",
     "header_bookmarks": "ఇటీవలి ఇష్టాంశములు",
     "header_recommended_by": "{provider}చే సిఫార్సు చేయబడినది",
     "header_bookmarks_placeholder": "మీకు ఇంకా ఎటువంటి ఇష్టాంశాలు లేవు.",
     "header_stories_from": "నుండి",
     "type_label_visited": "సందర్శించినవి",
     "type_label_bookmarked": "ఇష్టాంశము చేయబడినది",
     "type_label_synced": "మరో పరికరం నుంచి సమకాలీకరించి తెచ్చుకున్నవి",
@@ -5751,37 +5795,40 @@
     "confirm_history_delete_notice_p2": "ఈ చర్యను రద్దు చేయలేము.",
     "menu_action_save_to_pocket": "Pocket కి సేవ్ చేయండి",
     "search_for_something_with": "{search_term} కోసం దీని సాయంతో వెతుకు:",
     "search_button": "వెతకండి",
     "search_header": "{search_engine_name} శోధన",
     "search_web_placeholder": "జాలంలో వెతకండి",
     "search_settings": "శోధన అమరికలు మార్చు",
     "section_info_option": "సమాచారం",
+    "section_info_send_feedback": "అభిప్రాయాన్ని పంపండి",
+    "section_info_privacy_notice": "గోప్యతా విధానం",
     "welcome_title": "కొత్త ట్యాబుకు స్వాగతం",
     "welcome_body": "సముచితమైన మీ ఇష్టాంశాలను, వ్యాసాలను, వీడియోలను, ఇంకా మీరు ఇటీవలే చూసిన పేజీలను మీకు తేలిగ్గా అందుబాటులో ఉంచేందుకు Firefox ఈ జాగాని వాడుకుంటుంది.",
     "welcome_label": "మీ ముఖ్యాంశాలను గుర్తిస్తున్నది",
     "time_label_less_than_minute": "<1ని",
     "time_label_minute": "{number}ని",
     "time_label_hour": "{number}గం",
     "time_label_day": "{number}రో",
     "settings_pane_button_label": "మీ కొత్త ట్యాబు పేజీని మలచుకోండి",
     "settings_pane_header": "కొత్త ట్యాబు అభిరుచులు",
-    "settings_pane_body": "మీరు కొత్త ట్యాబు తెరిచినప్పుడు ఏం చూడాలో ఎంచుకోండి.",
+    "settings_pane_body2": "మీరు ఈ పేజీలో చూసేదాన్ని ఎంచుకోండి.",
     "settings_pane_search_header": "వెతకడం",
     "settings_pane_search_body": "కొత్త ట్యాబు నుండే జాలంలో వెతకండి.",
     "settings_pane_topsites_header": "మేటి సైట్లు",
     "settings_pane_topsites_body": "మీరు ఎక్కువగా చూసే వెబ్‌సైట్లను చూడండి.",
     "settings_pane_topsites_options_showmore": "రెండు వరుసలు చూపించు",
     "settings_pane_bookmarks_header": "ఇటీవలి బుక్మార్క్లు",
     "settings_pane_bookmarks_body": "ఒక సులభ స్థానంలో మీ క్రొత్తగా సృష్టించిన బుక్మార్క్లు.",
     "settings_pane_visit_again_header": "మళ్లీ సందర్శించండి",
     "settings_pane_visit_again_body": "మీరు బ్రౌజింగ్ చరిత్రలో గుర్తుంచుకోవాల్సిన  లేదా తిరిగి పొందవలసిన భాగాలను చూపిస్తుంది.",
-    "settings_pane_pocketstories_header": "ముఖ్య కథనాలు",
-    "settings_pane_pocketstories_body": "Mozilla కుటుంబం యొక్క Pocket, మీరు కనుగొనలేకపోయే అధిక-నాణ్యత విషయముకి మిమ్మల్ని అనుసంధానించడానికి సహాయపడుతుంది.",
+    "settings_pane_highlights_header": "విశేషాలు",
+    "settings_pane_highlights_options_bookmarks": "ఇష్టాంశాలు",
+    "settings_pane_highlights_options_visited": "చూసిన సైటులు",
     "settings_pane_done_button": "పూర్తయింది",
     "edit_topsites_button_text": "మార్చు",
     "edit_topsites_button_label": "మీ మేటి సైట్ల విభాగాన్ని మలచుకోండి",
     "edit_topsites_showmore_button": "ఇంకా చూపించు",
     "edit_topsites_showless_button": "కొన్నే చూపించు",
     "edit_topsites_done_button": "పూర్తయింది",
     "edit_topsites_pin_button": "ఈ సైటును ఇక్కడ గుచ్చు",
     "edit_topsites_unpin_button": "ఈ సైటుకి పిన్నుని తీసివేయండి",
@@ -5794,20 +5841,17 @@
     "topsites_form_url_placeholder": "URL ను టైప్ చేయండి లేదా అతికించండి",
     "topsites_form_add_button": "చేర్చు",
     "topsites_form_save_button": "భద్రపరచు",
     "topsites_form_cancel_button": "రద్దుచేయి",
     "topsites_form_url_validation": "చెల్లుబాటు అయ్యే URL అవసరం",
     "pocket_read_more": "ప్రముఖ అంశాలు:",
     "pocket_read_even_more": "మరిన్ని కథలను వీక్షించండి",
     "pocket_feedback_header": "వెబ్లో అత్యుత్తమమైనది, 25 మిలియన్లకు పైగా ప్రజలు పర్యవేక్షించినవి.",
-    "pocket_feedback_body": "Mozilla కుటుంబం యొక్క Pocket, మీరు కనుగొనలేకపోయే అధిక-నాణ్యత విషయముకి మిమ్మల్ని అనుసంధానించడానికి సహాయపడుతుంది.",
-    "pocket_send_feedback": "అభిప్రాయాన్ని పంపండి",
     "topstories_empty_state": "మీరు పట్టుబడ్డారు. {provider} నుండి మరింత అగ్ర కథనాల కోసం తరువాత తనిఖీ చేయండి. వేచి ఉండలేరా? జాలములోని అంతటి నుండి మరింత గొప్ప కథనాలను కనుగొనడానికి ప్రసిద్ధ అంశం ఎంచుకోండి.",
-    "manual_migration_explanation": "మరొక విహరణి నుండి మీకు ఇష్టమైన సైట్లు మరియు ఇష్టంశాలతో Firefox ను ప్రయత్నించండి.",
     "manual_migration_cancel_button": "అడిగినందుకు ధన్యవాదాలు, వద్దు",
     "manual_migration_import_button": "ఇప్పుడే దిగుమతి చేయండి"
   },
   "th": {
     "newtab_page_title": "แท็บใหม่",
     "default_label_loading": "กำลังโหลด…",
     "header_top_sites": "ไซต์เด่น",
     "header_stories": "เรื่องราวเด่น",
@@ -5860,16 +5904,17 @@
     "settings_pane_topsites_header": "ไซต์เด่น",
     "settings_pane_topsites_body": "เข้าถึงเว็บไซต์ที่คุณเยี่ยมชมมากที่สุด",
     "settings_pane_topsites_options_showmore": "แสดงสองแถว",
     "settings_pane_bookmarks_header": "ที่คั่นหน้าเมื่อเร็ว ๆ นี้",
     "settings_pane_bookmarks_body": "ที่คั่นหน้าที่สร้างใหม่ของคุณในตำแหน่งที่ตั้งเดียวที่สะดวก",
     "settings_pane_visit_again_header": "เยี่ยมชมอีกครั้ง",
     "settings_pane_highlights_header": "รายการเด่น",
     "settings_pane_highlights_options_bookmarks": "ที่คั่นหน้า",
+    "settings_pane_highlights_options_visited": "ไซต์ที่เยี่ยมชมแล้ว",
     "settings_pane_done_button": "เสร็จสิ้น",
     "edit_topsites_button_text": "แก้ไข",
     "edit_topsites_button_label": "ปรับแต่งส่วนไซต์เด่นของคุณ",
     "edit_topsites_showmore_button": "แสดงเพิ่มเติม",
     "edit_topsites_showless_button": "แสดงน้อยลง",
     "edit_topsites_done_button": "เสร็จสิ้น",
     "edit_topsites_pin_button": "ปักหมุดไซต์นี้",
     "edit_topsites_unpin_button": "ถอนหมุดไซต์นี้",
@@ -5882,16 +5927,17 @@
     "topsites_form_url_placeholder": "พิมพ์หรือวาง URL",
     "topsites_form_add_button": "เพิ่ม",
     "topsites_form_save_button": "บันทึก",
     "topsites_form_cancel_button": "ยกเลิก",
     "topsites_form_url_validation": "ต้องการ URL ที่ถูกต้อง",
     "pocket_read_more": "หัวข้อยอดนิยม:",
     "pocket_read_even_more": "ดูเรื่องราวเพิ่มเติม",
     "pocket_feedback_header": "ที่สุดของเว็บ จัดรายการโดยผู้คนกว่า 25 ล้านคน",
+    "manual_migration_explanation2": "ลอง Firefox ด้วยที่คั่นหน้า, ประวัติ และรหัสผ่านจากเบราว์เซอร์อื่น",
     "manual_migration_cancel_button": "ไม่ ขอบคุณ",
     "manual_migration_import_button": "นำเข้าตอนนี้"
   },
   "tl": {
     "newtab_page_title": "Bagong Tab",
     "default_label_loading": "Pagkarga…",
     "header_top_sites": "Tuktok na mga Site",
     "header_highlights": "Highlights",
@@ -6289,18 +6335,18 @@
     "type_label_recommended": "趋势",
     "type_label_open": "打开",
     "type_label_topic": "主题",
     "type_label_now": "现在",
     "menu_action_bookmark": "添加书签",
     "menu_action_remove_bookmark": "移除书签",
     "menu_action_copy_address": "复制地址",
     "menu_action_email_link": "用邮件发送链接…",
-    "menu_action_open_new_window": "在新窗口中打开",
-    "menu_action_open_private_window": "在新的隐私窗口中打开",
+    "menu_action_open_new_window": "新建窗口打开",
+    "menu_action_open_private_window": "新建隐私浏览窗口打开",
     "menu_action_dismiss": "隐藏",
     "menu_action_delete": "从历史记录中删除",
     "menu_action_pin": "固定",
     "menu_action_unpin": "取消固定",
     "confirm_history_delete_p1": "确定删除此页面在您的历史记录中的所有记录?",
     "confirm_history_delete_notice_p2": "此操作不能撤销。",
     "menu_action_save_to_pocket": "保存到 Pocket",
     "search_for_something_with": "搜索 {search_term},使用:",
--- a/browser/extensions/activity-stream/install.rdf.in
+++ b/browser/extensions/activity-stream/install.rdf.in
@@ -3,17 +3,17 @@
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>activity-stream@mozilla.org</em:id>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:unpack>false</em:unpack>
-    <em:version>2017.09.08.0882-3dbf720c</em:version>
+    <em:version>2017.09.11.1306-373d9fc</em:version>
     <em:name>Activity Stream</em:name>
     <em:description>A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you're looking for in Firefox.</em:description>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
--- a/browser/extensions/activity-stream/lib/ActivityStream.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm
@@ -72,16 +72,20 @@ const PREFS_CONFIG = new Map([
   ["migrationLastShownDate", {
     title: "Timestamp when migration message was last shown. In seconds.",
     value: 0
   }],
   ["migrationRemainingDays", {
     title: "Number of days to show the manual migration message",
     value: 4
   }],
+  ["prerender", {
+    title: "Use the prerendered version of activity-stream.html. This is set automatically by PrefsFeed.jsm.",
+    value: true
+  }],
   ["showSearch", {
     title: "Show the Search bar on the New Tab page",
     value: true
   }],
   ["showTopSites", {
     title: "Show the Top Sites section on the New Tab page",
     value: true
   }],
--- a/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/HighlightsFeed.jsm
@@ -5,23 +5,25 @@
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
 
 const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
 const {SectionsManager} = Cu.import("resource://activity-stream/lib/SectionsManager.jsm", {});
+const {TOP_SITES_SHOWMORE_LENGTH} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
 const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
   "resource://gre/modules/NewTabUtils.jsm");
 
 const HIGHLIGHTS_MAX_LENGTH = 9;
 const HIGHLIGHTS_UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
+const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_SHOWMORE_LENGTH;
 const SECTION_ID = "highlights";
 
 this.HighlightsFeed = class HighlightsFeed {
   constructor() {
     this.highlightsLastUpdated = 0;
     this.highlights = [];
     this.dedupe = new Dedupe(this._dedupeKey);
   }
@@ -38,29 +40,50 @@ this.HighlightsFeed = class HighlightsFe
     SectionsManager.enableSection(SECTION_ID);
   }
 
   uninit() {
     SectionsManager.disableSection(SECTION_ID);
   }
 
   async fetchHighlights(broadcast = false) {
-    this.highlights = await NewTabUtils.activityStreamLinks.getHighlights();
-    for (let highlight of this.highlights) {
-      highlight.hostname = shortURL(Object.assign({}, highlight, {url: highlight.url}));
-      highlight.image = highlight.preview_image_url;
-      if (highlight.bookmarkGuid) {
-        highlight.type = "bookmark";
+    // Request more than the expected length to allow for items being removed by
+    // deduping against Top Sites or multiple history from the same domain, etc.
+    const manyPages = await NewTabUtils.activityStreamLinks.getHighlights({numItems: MANY_EXTRA_LENGTH});
+
+    // Remove any Highlights that are in Top Sites already
+    const deduped = this.dedupe.group(this.store.getState().TopSites.rows, manyPages)[1];
+
+    // Keep all "bookmark"s and at most one (most recent) "history" per host
+    this.highlights = [];
+    const hosts = new Set();
+    for (const page of deduped) {
+      const hostname = shortURL(page);
+      // Skip this history page if we already something from the same host
+      if (page.type === "history" && hosts.has(hostname)) {
+        continue;
+      }
+
+      // We want the page, so update various fields for UI
+      Object.assign(page, {
+        hostname,
+        image: page.preview_image_url,
+        type: page.bookmarkGuid ? "bookmark" : page.type
+      });
+
+      // Add the "bookmark" or not-skipped "history"
+      this.highlights.push(page);
+      hosts.add(hostname);
+
+      // Skip the rest if we have enough items
+      if (this.highlights.length === HIGHLIGHTS_MAX_LENGTH) {
+        break;
       }
     }
 
-    // Remove any Highlights that are in Top Sites already
-    const deduped = this.dedupe.group(this.store.getState().TopSites.rows, this.highlights);
-    this.highlights = deduped[1];
-
     SectionsManager.updateSection(SECTION_ID, {rows: this.highlights}, this.highlightsLastUpdated === 0 || broadcast);
     this.highlightsLastUpdated = Date.now();
   }
 
   onAction(action) {
     switch (action.type) {
       case at.INIT:
         this.init();
--- a/browser/extensions/activity-stream/lib/NewTabInit.jsm
+++ b/browser/extensions/activity-stream/lib/NewTabInit.jsm
@@ -7,20 +7,38 @@ const {utils: Cu} = Components;
 
 const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
 
 /**
  * NewTabInit - A placeholder for now. This will send a copy of the state to all
  *              newly opened tabs.
  */
 this.NewTabInit = class NewTabInit {
+  constructor() {
+    this._queue = new Set();
+  }
+  reply(target) {
+    const action = {type: at.NEW_TAB_INITIAL_STATE, data: this.store.getState()};
+    this.store.dispatch(ac.SendToContent(action, target));
+  }
   onAction(action) {
-    let newAction;
     switch (action.type) {
-      case at.NEW_TAB_LOAD:
-        newAction = {type: at.NEW_TAB_INITIAL_STATE, data: this.store.getState()};
-        this.store.dispatch(ac.SendToContent(newAction, action.meta.fromTarget));
+      case at.NEW_TAB_STATE_REQUEST:
+        // If localization hasn't been loaded yet, we should wait for it.
+        if (!this.store.getState().App.strings) {
+          this._queue.add(action.meta.fromTarget);
+          return;
+        }
+        this.reply(action.meta.fromTarget);
+        break;
+      case at.LOCALE_UPDATED:
+        // If the queue is full because we were waiting for strings,
+        // dispatch them now.
+        if (this._queue.size > 0 && this.store.getState().App.strings) {
+          this._queue.forEach(target => this.reply(target));
+          this._queue.clear();
+        }
         break;
     }
   }
 };
 
 this.EXPORTED_SYMBOLS = ["NewTabInit"];
--- a/browser/extensions/activity-stream/lib/PrefsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/PrefsFeed.jsm
@@ -2,38 +2,62 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {utils: Cu} = Components;
 
 const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
 const {Prefs} = Cu.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
+const {PrerenderData} = Cu.import("resource://activity-stream/common/PrerenderData.jsm", {});
 
 this.PrefsFeed = class PrefsFeed {
   constructor(prefMap) {
     this._prefMap = prefMap;
     this._prefs = new Prefs();
   }
+
+  // If the any prefs are set to something other than what the prerendered version
+  // of AS expects, we can't use it.
+  _setPrerenderPref() {
+    for (const prefName of PrerenderData.invalidatingPrefs) {
+      if (this._prefs.get(prefName) !== PrerenderData.initialPrefs[prefName]) {
+        this._prefs.set("prerender", false);
+        return;
+      }
+    }
+    this._prefs.set("prerender", true);
+  }
+
+  _checkPrerender(name) {
+    if (PrerenderData.invalidatingPrefs.includes(name)) {
+      this._setPrerenderPref();
+    }
+  }
+
   onPrefChanged(name, value) {
     if (this._prefMap.has(name)) {
       this.store.dispatch(ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name, value}}));
     }
+    this._checkPrerender(name, value);
   }
+
   init() {
     this._prefs.observeBranch(this);
 
     // Get the initial value of each activity stream pref
     const values = {};
     for (const name of this._prefMap.keys()) {
       values[name] = this._prefs.get(name);
     }
 
     // Set the initial state of all prefs in redux
     this.store.dispatch(ac.BroadcastToContent({type: at.PREFS_INITIAL_VALUES, data: values}));
+
+    this._setPrerenderPref();
   }
   removeListeners() {
     this._prefs.ignoreBranch(this);
   }
   onAction(action) {
     switch (action.type) {
       case at.INIT:
         this.init();
--- a/browser/extensions/activity-stream/lib/SectionsManager.jsm
+++ b/browser/extensions/activity-stream/lib/SectionsManager.jsm
@@ -43,17 +43,17 @@ const BUILT_IN_SECTIONS = {
       titleString: {id: "settings_pane_highlights_header"},
       descString: {id: "settings_pane_highlights_body2"}
     },
     shouldHidePref:  false,
     eventSource: "HIGHLIGHTS",
     icon: "highlights",
     title: {id: "header_highlights"},
     maxRows: 3,
-    availableContextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
+    availableContextMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
     emptyState: {
       message: {id: "highlights_empty_state"},
       icon: "highlights"
     },
     order: 1
   })
 };
 
--- a/browser/extensions/activity-stream/lib/SnippetsFeed.jsm
+++ b/browser/extensions/activity-stream/lib/SnippetsFeed.jsm
@@ -116,17 +116,17 @@ this.SnippetsFeed = class SnippetsFeed {
   }
 
   uninit() {
     Services.prefs.removeObserver(ONBOARDING_FINISHED_PREF, this._refresh);
     Services.prefs.removeObserver(SNIPPETS_URL_PREF, this._refresh);
     Services.prefs.removeObserver(TELEMETRY_PREF, this._refresh);
     Services.prefs.removeObserver(FXA_USERNAME_PREF, this._refresh);
     Services.obs.removeObserver(this, SEARCH_ENGINE_OBSERVER_TOPIC);
-    this.store.dispatch({type: at.SNIPPETS_RESET});
+    this.store.dispatch(ac.BroadcastToContent({type: at.SNIPPETS_RESET}));
   }
 
   showFirefoxAccounts(browser) {
     // We want to replace the current tab.
     browser.loadURI("about:accounts?action=signup&entrypoint=snippets");
   }
 
   onAction(action) {
--- a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
@@ -16,16 +16,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Screenshots",
   "resource://activity-stream/lib/Screenshots.jsm");
 
 const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
 const DEFAULT_SITES_PREF = "default.sites";
 const DEFAULT_TOP_SITES = [];
 const FRECENCY_THRESHOLD = 100; // 1 visit (skip first-run/one-time pages)
+const MIN_FAVICON_SIZE = 96;
 
 this.TopSitesFeed = class TopSitesFeed {
   constructor() {
     this.lastUpdated = 0;
     this._tippyTopProvider = new TippyTopProvider();
     this.dedupe = new Dedupe(this._dedupeKey);
   }
   _dedupeKey(site) {
@@ -90,23 +91,23 @@ this.TopSitesFeed = class TopSitesFeed {
     // First, cache existing screenshots in case we need to reuse them
     const currentScreenshots = {};
     for (const link of this.store.getState().TopSites.rows) {
       if (link && link.screenshot) {
         currentScreenshots[link.url] = link.screenshot;
       }
     }
 
-    // Now, get a tippy top icon or screenshot for every item
+    // Now, get a tippy top icon, a rich icon, or screenshot for every item
     for (let link of links) {
       if (!link) { continue; }
 
-      // Check for tippy top icon.
+      // Check for tippy top icon or a rich icon.
       link = this._tippyTopProvider.processSite(link);
-      if (link.tippyTopIcon) { continue; }
+      if (link.tippyTopIcon || link.faviconSize >= MIN_FAVICON_SIZE) { continue; }
 
       // If no tippy top, then we get a screenshot.
       if (currentScreenshots[link.url]) {
         link.screenshot = currentScreenshots[link.url];
       } else {
         this.getScreenshot(link.url);
       }
     }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/activity-stream-prerender.test.jsx
@@ -0,0 +1,70 @@
+const prerender = require("content-src/activity-stream-prerender");
+const {prerenderStore} = prerender;
+const {PrerenderData} = require("common/PrerenderData.jsm");
+
+describe("prerenderStore", () => {
+  it("should create a store", () => {
+    const store = prerenderStore();
+
+    assert.isFunction(store.getState);
+  });
+  it("should start uninitialized", () => {
+    const store = prerenderStore();
+
+    const state = store.getState();
+    assert.equal(state.App.initialized, false);
+  });
+  it("should set the right locale, strings, and text direction", () => {
+    const strings = {foo: "foo"};
+
+    const store = prerenderStore("en-FOO", strings);
+
+    const state = store.getState();
+    assert.equal(state.App.locale, "en-FOO");
+    assert.equal(state.App.strings, strings);
+    assert.equal(state.App.textDirection, "ltr");
+  });
+  it("should add the right initial prefs", () => {
+    const store = prerenderStore();
+
+    const state = store.getState();
+    assert.equal(state.Prefs.values, PrerenderData.initialPrefs);
+  });
+  it("should add TopStories as the first section", () => {
+    const store = prerenderStore();
+
+    const state = store.getState();
+    // TopStories
+    const firstSection = state.Sections[0];
+    assert.equal(firstSection.id, "topstories");
+    // it should start uninitialized
+    assert.equal(firstSection.initialized, false);
+  });
+});
+
+describe("prerender", () => {
+  it("should set the locale and get the right strings of whatever is passed in", () => {
+    const {store} = prerender("en-US");
+
+    const state = store.getState();
+    assert.equal(state.App.locale, "en-US");
+    assert.equal(state.App.strings.newtab_page_title, "New Tab");
+  });
+  it("should throw if an unknown locale is passed in", () => {
+    assert.throws(() => prerender("en-FOO"));
+  });
+  it("should set the locale to en-PRERENDER and have empty strings if no locale is passed in", () => {
+    const {store} = prerender();
+
+    const state = store.getState();
+    assert.equal(state.App.locale, "en-PRERENDER");
+    assert.equal(state.App.strings.newtab_page_title, " ");
+  });
+  // # TODO: Remove when #3370 is resolved.
+  it("should render a real English string for search_web_placeholder", () => {
+    const {store} = prerender();
+
+    const state = store.getState();
+    assert.equal(state.App.strings.search_web_placeholder, "Search the Web");
+  });
+});
--- a/browser/extensions/activity-stream/test/unit/common/Reducers.test.js
+++ b/browser/extensions/activity-stream/test/unit/common/Reducers.test.js
@@ -21,22 +21,30 @@ describe("Reducers", () => {
 
       assert.propertyVal(nextState, "version", "1.2.3");
       assert.propertyVal(nextState, "locale", INITIAL_STATE.App.locale);
     });
     it("should not update state for empty action.data on LOCALE_UPDATED", () => {
       const nextState = App(undefined, {type: at.LOCALE_UPDATED});
       assert.equal(nextState, INITIAL_STATE.App);
     });
-    it("should set locale, strings on LOCALE_UPDATE", () => {
+    it("should set locale, strings and text direction on LOCALE_UPDATE", () => {
       const strings = {};
       const action = {type: "LOCALE_UPDATED", data: {locale: "zh-CN", strings}};
       const nextState = App(undefined, action);
       assert.propertyVal(nextState, "locale", "zh-CN");
       assert.propertyVal(nextState, "strings", strings);
+      assert.propertyVal(nextState, "textDirection", "ltr");
+    });
+    it("should set rtl text direction for RTL locales", () => {
+      const action = {type: "LOCALE_UPDATED", data: {locale: "ar"}};
+
+      const nextState = App(undefined, action);
+
+      assert.propertyVal(nextState, "textDirection", "rtl");
     });
   });
   describe("TopSites", () => {
     it("should return the initial state", () => {
       const nextState = TopSites(undefined, {type: "FOO"});
       assert.equal(nextState, INITIAL_STATE.TopSites);
     });
     it("should add top sites on TOP_SITES_UPDATED", () => {
--- a/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/HighlightsFeed.test.js
@@ -25,17 +25,17 @@ describe("Top Sites Feed", () => {
     fakeNewTabUtils = {activityStreamLinks: {getHighlights: sandbox.spy(() => Promise.resolve(links))}};
     sectionsManagerStub = {
       onceInitialized: sinon.stub().callsFake(callback => callback()),
       enableSection: sinon.spy(),
       disableSection: sinon.spy(),
       updateSection: sinon.spy(),
       sections: new Map([["highlights", {}]])
     };
-    shortURLStub = sinon.stub().callsFake(site => site.url);
+    shortURLStub = sinon.stub().callsFake(site => site.url.match(/\/([^/]+)/)[1]);
     globals.set("NewTabUtils", fakeNewTabUtils);
     ({HighlightsFeed, HIGHLIGHTS_UPDATE_TIME, SECTION_ID} = injector({
       "lib/ShortURL.jsm": {shortURL: shortURLStub},
       "lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub},
       "common/Dedupe.jsm": {Dedupe}
     }));
     feed = new HighlightsFeed();
     feed.store = {dispatch: sinon.spy(), getState() { return {TopSites: {rows: Array(12).fill(null).map((v, i) => ({url: `http://www.topsite${i}.com`}))}}; }};
@@ -67,30 +67,54 @@ describe("Top Sites Feed", () => {
 
       assert.notCalled(feed.fetchHighlights);
     });
   });
   describe("#fetchHighlights", () => {
     it("should add hostname and image to each link", async () => {
       links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
       await feed.fetchHighlights();
-      assert.equal(feed.highlights[0].hostname, links[0].url);
+      assert.equal(feed.highlights[0].hostname, "mozilla.org");
       assert.equal(feed.highlights[0].image, links[0].preview_image_url);
     });
     it("should not include any links already in Top Sites", async () => {
       links = [
         {url: "https://mozilla.org"},
         {url: "http://www.topsite0.com"},
         {url: "http://www.topsite1.com"},
         {url: "http://www.topsite2.com"}
       ];
       await feed.fetchHighlights();
       assert.equal(feed.highlights.length, 1);
       assert.deepEqual(feed.highlights[0], links[0]);
     });
+    it("should not include history of same hostname as a bookmark", async () => {
+      links = [
+        {url: "https://site.com/bookmark", type: "bookmark"},
+        {url: "https://site.com/history", type: "history"}
+      ];
+
+      await feed.fetchHighlights();
+
+      assert.equal(feed.highlights.length, 1);
+      assert.deepEqual(feed.highlights[0], links[0]);
+    });
+    it("should take the first history of a hostname", async () => {
+      links = [
+        {url: "https://site.com/first", type: "history"},
+        {url: "https://site.com/second", type: "history"},
+        {url: "https://other", type: "history"}
+      ];
+
+      await feed.fetchHighlights();
+
+      assert.equal(feed.highlights.length, 2);
+      assert.deepEqual(feed.highlights[0], links[0]);
+      assert.deepEqual(feed.highlights[1], links[2]);
+    });
     it("should set type to bookmark if there is a bookmarkGuid", async () => {
       links = [{url: "https://mozilla.org", type: "history", bookmarkGuid: "1234567890"}];
       await feed.fetchHighlights();
       assert.equal(feed.highlights[0].type, "bookmark");
     });
   });
   describe("#uninit", () => {
     it("should disable its section", () => {
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/unit/lib/NewTabInit.test.js
@@ -0,0 +1,62 @@
+const {NewTabInit} = require("lib/NewTabInit.jsm");
+const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
+
+describe("NewTabInit", () => {
+  let instance;
+  let store;
+  let STATE;
+  beforeEach(() => {
+    STATE = {};
+    store = {getState: sinon.stub().returns(STATE), dispatch: sinon.stub()};
+    instance = new NewTabInit();
+    instance.store = store;
+  });
+  it("should reply with a copy of the state immediately if localization is ready", () => {
+    STATE.App = {strings: {}};
+
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, 123));
+
+    const resp = ac.SendToContent({type: at.NEW_TAB_INITIAL_STATE, data: STATE}, 123);
+    assert.calledWith(store.dispatch, resp);
+  });
+  it("should not reply immediately if localization is not ready", () => {
+    STATE.App = {strings: null};
+
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, 123));
+
+    assert.notCalled(store.dispatch);
+  });
+  it("should dispatch responses for queued targets when LOCALE_UPDATED is received", () => {
+    STATE.App = {strings: null};
+
+    // Send requests before strings are ready
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "foo"));
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "bar"));
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "baz"));
+    assert.notCalled(store.dispatch);
+
+    // Update strings
+    STATE.App = {strings: {}};
+    instance.onAction({type: at.LOCALE_UPDATED});
+
+    assert.calledThrice(store.dispatch);
+    const action = {type: at.NEW_TAB_INITIAL_STATE, data: STATE};
+    assert.calledWith(store.dispatch, ac.SendToContent(action, "foo"));
+    assert.calledWith(store.dispatch, ac.SendToContent(action, "bar"));
+    assert.calledWith(store.dispatch, ac.SendToContent(action, "baz"));
+  });
+  it("should clear targets from the queue once they have been sent", () => {
+    STATE.App = {strings: null};
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "foo"));
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "bar"));
+    instance.onAction(ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST}, "baz"));
+
+    STATE.App = {strings: {}};
+    instance.onAction({type: at.LOCALE_UPDATED});
+    assert.calledThrice(store.dispatch);
+
+    store.dispatch.reset();
+    instance.onAction({type: at.LOCALE_UPDATED});
+    assert.notCalled(store.dispatch);
+  });
+});
--- a/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/PrefsFeed.test.js
@@ -1,21 +1,25 @@
 const {PrefsFeed} = require("lib/PrefsFeed.jsm");
 const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");
+const {PrerenderData} = require("common/PrerenderData.jsm");
+const {initialPrefs} = PrerenderData;
 
-const FAKE_PREFS = new Map([["foo", {value: 1}], ["bar", {value: 2}]]);
+const PRERENDER_PREF_NAME = "prerender";
 
 describe("PrefsFeed", () => {
   let feed;
+  let FAKE_PREFS;
   beforeEach(() => {
+    FAKE_PREFS = new Map([["foo", 1], ["bar", 2]]);
     feed = new PrefsFeed(FAKE_PREFS);
     feed.store = {dispatch: sinon.spy()};
     feed._prefs = {
-      get: sinon.spy(item => FAKE_PREFS.get(item).value),
-      set: sinon.spy(),
+      get: sinon.spy(item => FAKE_PREFS.get(item)),
+      set: sinon.spy((name, value) => FAKE_PREFS.set(name, value)),
       observe: sinon.spy(),
       observeBranch: sinon.spy(),
       ignore: sinon.spy(),
       ignoreBranch: sinon.spy()
     };
   });
   it("should set a pref when a SET_PREF action is received", () => {
     feed.onAction(ac.SetPref("foo", 2));
@@ -36,9 +40,47 @@ describe("PrefsFeed", () => {
     feed.onAction({type: at.UNINIT});
     assert.calledOnce(feed._prefs.ignoreBranch);
     assert.calledWith(feed._prefs.ignoreBranch, feed);
   });
   it("should send a PREF_CHANGED action when onPrefChanged is called", () => {
     feed.onPrefChanged("foo", 2);
     assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({type: at.PREF_CHANGED, data: {name: "foo", value: 2}}));
   });
+  describe("INIT prerendering", () => {
+    it("should set a prerender pref on init", () => {
+      feed.onAction({type: at.INIT});
+      assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME);
+    });
+    it("should set prerender pref to true if prefs match initial values", () => {
+      Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
+      feed.onAction({type: at.INIT});
+      assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, true);
+    });
+    it("should set prerender pref to false if a pref does not match its initial value", () => {
+      Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
+      FAKE_PREFS.set("feeds.section.topstories", false);
+      feed.onAction({type: at.INIT});
+      assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, false);
+    });
+  });
+  describe("onPrefChanged prerendering", () => {
+    it("should not change the prerender pref if the pref is not included in invalidatingPrefs", () => {
+      feed.onPrefChanged("foo123", true);
+      assert.notCalled(feed._prefs.set);
+    });
+    it("should set the prerender pref to false if a pref in invalidatingPrefs is changed from its original value", () => {
+      Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
+
+      feed._prefs.set("feeds.section.topstories", false);
+      feed.onPrefChanged("feeds.section.topstories", false);
+      assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, false);
+    });
+    it("should set the prerender pref back to true if the invalidatingPrefs are changed back to their original values", () => {
+      Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
+      FAKE_PREFS.set("feeds.section.topstories", false);
+
+      feed._prefs.set("feeds.section.topstories", true);
+      feed.onPrefChanged("feeds.section.topstories", true);
+      assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, true);
+    });
+  });
 });
--- a/browser/extensions/activity-stream/test/unit/lib/SnippetsFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/SnippetsFeed.test.js
@@ -1,10 +1,10 @@
 const {SnippetsFeed} = require("lib/SnippetsFeed.jsm");
-const {actionTypes: at} = require("common/Actions.jsm");
+const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm");
 const {GlobalOverrider} = require("test/unit/utils");
 
 const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000;
 const searchData = {searchEngineIdentifier: "google", engines: ["searchEngine-google", "searchEngine-bing"]};
 
 let overrider = new GlobalOverrider();
 
 describe("SnippetsFeed", () => {
@@ -73,23 +73,23 @@ describe("SnippetsFeed", () => {
   it("should call .uninit on an UNINIT action", () => {
     const feed = new SnippetsFeed();
     sandbox.stub(feed, "uninit");
     feed.store = {dispatch: sandbox.stub()};
 
     feed.onAction({type: at.UNINIT});
     assert.calledOnce(feed.uninit);
   });
-  it("should dispatch a SNIPPETS_RESET on uninit", () => {
+  it("should broadcast a SNIPPETS_RESET on uninit", () => {
     const feed = new SnippetsFeed();
     feed.store = {dispatch: sandbox.stub()};
 
     feed.uninit();
 
-    assert.calledWith(feed.store.dispatch, {type: at.SNIPPETS_RESET});
+    assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({type: at.SNIPPETS_RESET}));
   });
   it("should dispatch an update event when the Search observer is called", async () => {
     const feed = new SnippetsFeed();
     feed.store = {dispatch: sandbox.stub()};
     sandbox.stub(feed, "getSelectedSearchEngine")
       .returns(Promise.resolve(searchData));
 
     await feed.observe(null, "browser-search-engine-modified");
--- a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
@@ -290,16 +290,32 @@ describe("Top Sites Feed", () => {
         site.tippyTopIcon = "icon.png";
         site.backgroundColor = "#fff";
         return site;
       };
       await feed.refresh(action);
       assert.calledOnce(feed.store.dispatch);
       assert.notCalled(feed.getScreenshot);
     });
+    it("should skip getting screenshot if there is an icon of size greater than 96x96 and no tippy top", async () => {
+      sandbox.stub(feed, "getScreenshot");
+      feed.getLinksWithDefaults = () => [{
+        url: "foo.com",
+        favicon: "data:foo",
+        faviconSize: 196
+      }];
+      feed._tippyTopProvider.processSite = site => {
+        site.tippyTopIcon = null;
+        site.backgroundColor = null;
+        return site;
+      };
+      await feed.refresh(action);
+      assert.calledOnce(feed.store.dispatch);
+      assert.notCalled(feed.getScreenshot);
+    });
   });
   describe("getScreenshot", () => {
     it("should call Screenshots.getScreenshotForURL with the right url", async () => {
       const url = "foo.com";
       await feed.getScreenshot(url);
       assert.calledWith(fakeScreenshot.getScreenshotForURL, url);
     });
   });
--- a/browser/extensions/activity-stream/test/unit/lib/init-store.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/init-store.test.js
@@ -1,50 +1,111 @@
 const initStore = require("content-src/lib/init-store");
+const {MERGE_STORE_ACTION, rehydrationMiddleware} = initStore;
 const {GlobalOverrider, addNumberReducer} = require("test/unit/utils");
-const {actionCreators: ac} = require("common/Actions.jsm");
+const {actionCreators: ac, actionTypes: at} = require("common/Actions.jsm");
 
 describe("initStore", () => {
   let globals;
   let store;
   beforeEach(() => {
     globals = new GlobalOverrider();
     globals.set("sendAsyncMessage", globals.sandbox.spy());
     globals.set("addMessageListener", globals.sandbox.spy());
     store = initStore({number: addNumberReducer});
   });
   afterEach(() => globals.restore());
   it("should create a store with the provided reducers", () => {
     assert.ok(store);
     assert.property(store.getState(), "number");
   });
-  it("should add a listener for incoming actions", () => {
+  it("should add a listener that dispatches actions", () => {
     assert.calledWith(global.addMessageListener, initStore.INCOMING_MESSAGE_NAME);
-    const callback = global.addMessageListener.firstCall.args[1];
+    const listener = global.addMessageListener.firstCall.args[1];
     globals.sandbox.spy(store, "dispatch");
     const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: "FOO"}};
-    callback(message);
+
+    listener(message);
+
     assert.calledWith(store.dispatch, message.data);
   });
+  it("should not throw if addMessageListener is not defined", () => {
+    // Note: this is being set/restored by GlobalOverrider
+    delete global.addMessageListener;
+
+    assert.doesNotThrow(() => initStore({number: addNumberReducer}));
+  });
+  it("should initialize with an initial state if provided as the second argument", () => {
+    store = initStore({number: addNumberReducer}, {number: 42});
+
+    assert.equal(store.getState().number, 42);
+  });
   it("should log errors from failed messages", () => {
     const callback = global.addMessageListener.firstCall.args[1];
     globals.sandbox.stub(global.console, "error");
     globals.sandbox.stub(store, "dispatch").throws(Error("failed"));
 
-    const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: "FOO"}};
+    const message = {name: initStore.INCOMING_MESSAGE_NAME, data: {type: MERGE_STORE_ACTION}};
     callback(message);
 
     assert.calledOnce(global.console.error);
   });
   it("should replace the state if a MERGE_STORE_ACTION is dispatched", () => {
     store.dispatch({type: initStore.MERGE_STORE_ACTION, data: {number: 42}});
     assert.deepEqual(store.getState(), {number: 42});
   });
-  it("should send out SendToMain ations", () => {
+  it("should send out SendToMain actions", () => {
     const action = ac.SendToMain({type: "FOO"});
     store.dispatch(action);
     assert.calledWith(global.sendAsyncMessage, initStore.OUTGOING_MESSAGE_NAME, action);
   });
-  it("should not send out other types of ations", () => {
+  it("should not send out other types of actions", () => {
     store.dispatch({type: "FOO"});
     assert.notCalled(global.sendAsyncMessage);
   });
+  describe("rehydrationMiddleware", () => {
+    it("should allow NEW_TAB_STATE_REQUEST to go through", () => {
+      const action = ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST});
+      const next = sinon.spy();
+      rehydrationMiddleware(store)(next)(action);
+      assert.calledWith(next, action);
+    });
+    it("should dispatch an additional NEW_TAB_STATE_REQUEST if INIT was received after a request", () => {
+      const requestAction = ac.SendToMain({type: at.NEW_TAB_STATE_REQUEST});
+      const next = sinon.spy();
+
+      rehydrationMiddleware(store)(next)(requestAction);
+
+      next.reset();
+      rehydrationMiddleware(store)(next)({type: at.INIT});
+      assert.calledWith(next, requestAction);
+    });
+    it("should allow MERGE_STORE_ACTION to go through", () => {
+      const action = {type: MERGE_STORE_ACTION};
+      const next = sinon.spy();
+      rehydrationMiddleware(store)(next)(action);
+      assert.calledWith(next, action);
+    });
+    it("should not allow actions from main to go through before MERGE_STORE_ACTION was received", () => {
+      const next = sinon.spy();
+
+      rehydrationMiddleware(store)(next)(ac.BroadcastToContent({type: "FOO"}));
+      rehydrationMiddleware(store)(next)(ac.SendToContent({type: "FOO"}, 123));
+
+      assert.notCalled(next);
+    });
+    it("should allow all local actions to go through", () => {
+      const action = {type: "FOO"};
+      const next = sinon.spy();
+      rehydrationMiddleware(store)(next)(action);
+      assert.calledWith(next, action);
+    });
+    it("should allow actions from main to go through after MERGE_STORE_ACTION has been received", () => {
+      const next = sinon.spy();
+      rehydrationMiddleware(store)(next)({type: MERGE_STORE_ACTION});
+      next.reset();
+
+      const action = ac.SendToContent({type: "FOO"}, 123);
+      rehydrationMiddleware(store)(next)(action);
+      assert.calledWith(next, action);
+    });
+  });
 });
--- a/browser/extensions/formautofill/content/manageCreditCards.xhtml
+++ b/browser/extensions/formautofill/content/manageCreditCards.xhtml
@@ -12,26 +12,26 @@
 </head>
 <body>
   <fieldset>
     <legend data-localization="creditCardsListHeader"/>
     <select id="credit-cards" size="9" multiple="multiple"/>
   </fieldset>
   <div id="controls-container">
     <button id="remove" disabled="disabled" data-localization="remove"/>
-    <button id="show-credit-cards" data-localization="showCreditCards"/>
+    <button id="show-hide-credit-cards" data-localization="showCreditCards"/>
     <button id="add" data-localization="add"/>
     <button id="edit" disabled="disabled" data-localization="edit"/>
   </div>
   <script type="application/javascript">
     "use strict";
     /* global ManageCreditCards */
     new ManageCreditCards({
       records: document.getElementById("credit-cards"),
       controlsContainer: document.getElementById("controls-container"),
       remove: document.getElementById("remove"),
-      showCreditCards: document.getElementById("show-credit-cards"),
+      showHideCreditCards: document.getElementById("show-hide-credit-cards"),
       add: document.getElementById("add"),
       edit: document.getElementById("edit"),
     });
   </script>
 </body>
 </html>
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -23,17 +23,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
 
 class ManageRecords {
   constructor(subStorageName, elements) {
     this._storageInitPromise = profileStorage.initialize();
     this._subStorageName = subStorageName;
     this._elements = elements;
-    this._records = [];
     this._newRequest = false;
     this._isLoadingRecords = false;
     this.prefWin = window.opener;
     this.localizeDocument();
     window.addEventListener("DOMContentLoaded", this, {once: true});
   }
 
   async init() {
@@ -147,16 +146,17 @@ class ManageRecords {
     // Pause listening to storage change event to avoid triggering `loadRecords`
     // when removing records
     Services.obs.removeObserver(this, "formautofill-storage-changed");
 
     for (let option of options) {
       storage.remove(option.value);
       option.remove();
     }
+    this.updateButtonsStates(this._selectedOptions);
 
     // Resume listening to storage change event
     Services.obs.addObserver(this, "formautofill-storage-changed");
     // For testing only: notify record(s) has been removed
     this._elements.records.dispatchEvent(new CustomEvent("RecordsRemoved"));
   }
 
   /**
@@ -322,31 +322,32 @@ class ManageAddresses extends ManageReco
     }
     return parts.join(", ");
   }
 }
 
 class ManageCreditCards extends ManageRecords {
   constructor(elements) {
     super("creditCards", elements);
-    this.hasMasterPassword = MasterPassword.isEnabled;
-    if (this.hasMasterPassword) {
-      elements.showCreditCards.setAttribute("hidden", true);
+    this._hasMasterPassword = MasterPassword.isEnabled;
+    this._isDecrypted = false;
+    if (this._hasMasterPassword) {
+      elements.showHideCreditCards.setAttribute("hidden", true);
     }
   }
 
   /**
    * Open the edit address dialog to create/edit a credit card.
    *
    * @param  {object} creditCard [optional]
    */
   async openEditDialog(creditCard) {
     // If master password is set, ask for password if user is trying to edit an
     // existing credit card.
-    if (!this.hasMasterPassword || !creditCard || await MasterPassword.prompt()) {
+    if (!this._hasMasterPassword || !creditCard || await MasterPassword.prompt()) {
       this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, null, creditCard);
     }
   }
 
   /**
    * Get credit card display label. It should display masked numbers and the
    * cardholder's name, separated by a comma. If `showCreditCards` is set to
    * true, decrypted credit card numbers are shown instead.
@@ -368,23 +369,51 @@ class ManageCreditCards extends ManageRe
       parts.push(ccLabel);
     }
     if (creditCard["cc-name"]) {
       parts.push(creditCard["cc-name"]);
     }
     return parts.join(", ");
   }
 
-  async decryptOptions(options) {
+  async toggleShowHideCards(options) {
+    this._isDecrypted = !this._isDecrypted;
+    this.updateShowHideButtonState();
+    await this.updateLabels(options, this._isDecrypted);
+  }
+
+  async updateLabels(options, isDecrypted) {
     for (let option of options) {
-      option.text = await this.getLabel(option.record, true);
+      option.text = await this.getLabel(option.record, isDecrypted);
     }
-    // For testing only: Notify when credit cards have been decrypted
-    this._elements.records.dispatchEvent(new CustomEvent("OptionsDecrypted"));
+    // For testing only: Notify when credit cards labels have been updated
+    this._elements.records.dispatchEvent(new CustomEvent("LabelsUpdated"));
+  }
+
+  async renderRecordElements(records) {
+    // Revert back to encrypted form when re-rendering happens
+    this._isDecrypted = false;
+    await super.renderRecordElements(records);
+  }
+
+  updateButtonsStates(selectedCount) {
+    this.updateShowHideButtonState();
+    super.updateButtonsStates(selectedCount);
+  }
+
+  updateShowHideButtonState() {
+    if (this._elements.records.length) {
+      this._elements.showHideCreditCards.removeAttribute("disabled");
+    } else {
+      this._elements.showHideCreditCards.setAttribute("disabled", true);
+    }
+    this._elements.showHideCreditCards.textContent =
+      this._isDecrypted ? FormAutofillUtils.stringBundle.GetStringFromName("hideCreditCards") :
+                          FormAutofillUtils.stringBundle.GetStringFromName("showCreditCards");
   }
 
   handleClick(event) {
-    if (event.target == this._elements.showCreditCards) {
-      this.decryptOptions(this._elements.records.options);
+    if (event.target == this._elements.showHideCreditCards) {
+      this.toggleShowHideCards(this._elements.records.options);
     }
     super.handleClick(event);
   }
 }
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -41,16 +41,17 @@ fieldNameSeparator = ,\u0020
 phishingWarningMessage = Also autofills %S
 phishingWarningMessage2 = Autofills %S
 
 manageAddressesTitle = Saved Addresses
 manageCreditCardsTitle = Saved Credit Cards
 addressesListHeader = Addresses
 creditCardsListHeader = Credit Cards
 showCreditCards = Show Credit Cards
+hideCreditCards = Hide Credit Cards
 remove = Remove
 add = Add…
 edit = Edit…
 
 addNewAddressTitle = Add New Address
 editAddressTitle = Edit Address
 givenName = First Name
 additionalName = Middle Name
--- a/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
@@ -1,34 +1,34 @@
 "use strict";
 
 Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
 
 const TEST_SELECTORS = {
   selRecords: "#credit-cards",
   btnRemove: "#remove",
-  btnShowCreditCards: "#show-credit-cards",
+  btnShowHideCreditCards: "#show-hide-credit-cards",
   btnAdd: "#add",
   btnEdit: "#edit",
 };
 
 const DIALOG_SIZE = "width=600,height=400";
 
 add_task(async function test_manageCreditCardsInitialState() {
   await BrowserTestUtils.withNewTab({gBrowser, url: MANAGE_CREDIT_CARDS_DIALOG_URL}, async function(browser) {
     await ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
       let selRecords = content.document.querySelector(args.selRecords);
       let btnRemove = content.document.querySelector(args.btnRemove);
-      let btnShowCreditCards = content.document.querySelector(args.btnShowCreditCards);
+      let btnShowHideCreditCards = content.document.querySelector(args.btnShowHideCreditCards);
       let btnAdd = content.document.querySelector(args.btnAdd);
       let btnEdit = content.document.querySelector(args.btnEdit);
 
       is(selRecords.length, 0, "No credit card");
       is(btnRemove.disabled, true, "Remove button disabled");
-      is(btnShowCreditCards.disabled, false, "Show Credit Cards button disabled");
+      is(btnShowHideCreditCards.disabled, true, "Show Credit Cards button disabled");
       is(btnAdd.disabled, false, "Add button enabled");
       is(btnEdit.disabled, true, "Edit button disabled");
     });
   });
 });
 
 add_task(async function test_cancelManageCreditCardsDialogWithESC() {
   await new Promise(resolve => {
@@ -99,46 +99,72 @@ add_task(async function test_showCreditC
   await saveCreditCard(TEST_CREDIT_CARD_1);
   await saveCreditCard(TEST_CREDIT_CARD_2);
   await saveCreditCard(TEST_CREDIT_CARD_3);
 
   let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
   await BrowserTestUtils.waitForEvent(win, "FormReady");
 
   let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
-  let btnShowCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowCreditCards);
+  let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
 
-  EventUtils.synthesizeMouseAtCenter(btnShowCreditCards, {}, win);
-  await BrowserTestUtils.waitForEvent(selRecords, "OptionsDecrypted");
+  is(btnShowHideCreditCards.disabled, false, "Show credit cards button enabled");
+  is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
 
+  // Show credit card numbers
+  EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+  await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
   is(selRecords[0].text, "9999888877776666", "Decrypted credit card 3");
   is(selRecords[1].text, "1111222233334444, Timothy Berners-Lee", "Decrypted credit card 2");
   is(selRecords[2].text, "1234567812345678, John Doe", "Decrypted credit card 1");
+  is(btnShowHideCreditCards.textContent, "Hide Credit Cards", "Label should be 'Hide Credit Cards'");
 
+  // Hide credit card numbers
+  EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+  await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
+  is(selRecords[0].text, "**** 6666", "Masked credit card 3");
+  is(selRecords[1].text, "**** 4444, Timothy Berners-Lee", "Masked credit card 2");
+  is(selRecords[2].text, "**** 5678, John Doe", "Masked credit card 1");
+  is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
+
+  // Show credit card numbers again to test if they revert back to masked form when reloaded
+  EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+  await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
+  // Ensure credit card numbers are shown again
+  is(selRecords[0].text, "9999888877776666", "Decrypted credit card 3");
+  // Remove a card to trigger reloading
   await removeCreditCards([selRecords.options[2].value]);
+  await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+  is(selRecords[0].text, "**** 6666", "Masked credit card 3");
+  is(selRecords[1].text, "**** 4444, Timothy Berners-Lee", "Masked credit card 2");
+
+  // Remove the rest of the cards
   await removeCreditCards([selRecords.options[1].value]);
   await removeCreditCards([selRecords.options[0].value]);
+  await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+  is(btnShowHideCreditCards.disabled, true, "Show credit cards button is disabled when there is no card");
+
   win.close();
 });
 
 add_task(async function test_hasMasterPassword() {
   await saveCreditCard(TEST_CREDIT_CARD_1);
   LoginTestUtils.masterPassword.enable();
 
   let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
   await BrowserTestUtils.waitForEvent(win, "FormReady");
 
   let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
   let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
-  let btnShowCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowCreditCards);
+  let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
   let btnAdd = win.document.querySelector(TEST_SELECTORS.btnAdd);
   let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
   let masterPasswordDialogShown = waitForMasterPasswordDialog();
 
-  is(btnShowCreditCards.hidden, true, "Show credit cards button is hidden");
+  is(btnShowHideCreditCards.hidden, true, "Show credit cards button is hidden");
 
   // Master password dialog should show when trying to edit a credit card record.
   EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
   EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
   await masterPasswordDialogShown;
 
   // Master password is not required for removing credit cards.
   EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -95,16 +95,17 @@ webextPerms.optionalPermsAllow.accessKey
 webextPerms.optionalPermsDeny.label=Deny
 webextPerms.optionalPermsDeny.accessKey=D
 
 webextPerms.description.bookmarks=Read and modify bookmarks
 webextPerms.description.browserSettings=Read and modify browser settings
 webextPerms.description.browsingData=Clear recent browsing history, cookies, and related data
 webextPerms.description.clipboardRead=Get data from the clipboard
 webextPerms.description.clipboardWrite=Input data to the clipboard
+webextPerms.description.devtools=Extend developer tools to access your data in open tabs
 webextPerms.description.downloads=Download files and read and modify the browser’s download history
 webextPerms.description.downloads.open=Open files downloaded to your computer
 webextPerms.description.find=Read the text of all open tabs
 webextPerms.description.geolocation=Access your location
 webextPerms.description.history=Access browsing history
 webextPerms.description.management=Monitor extension usage and manage themes
 # LOCALIZATION NOTE (webextPerms.description.nativeMessaging)
 # %S will be replaced with the name of the application
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -52,17 +52,24 @@
 }
 
 /** Begin titlebar **/
 
 #titlebar-buttonbox > .titlebar-button {
   display: none;
 }
 
-/* NB: these would be margin-inline-start/end if it wasn't for the fact that OS X
+/* Making the toolbox position:relative (browser.inc.css) occludes titlebar indicators
+ * if the toolbox has a background. Fix this by positioning the relevant elements, too: */
+#titlebar-secondary-buttonbox {
+  position: relative;
+  z-index: 1;
+}
+
+/* These would be margin-inline-start/end if it wasn't for the fact that OS X
  * doesn't reverse the order of the items in the titlebar in RTL mode. */
 .titlebar-placeholder[type="caption-buttons"],
 #titlebar-buttonbox {
   margin-left: 7px;
 }
 
 .titlebar-placeholder[type="fullscreen-button"],
 #titlebar-secondary-buttonbox {
--- a/browser/themes/shared/plugin-doorhanger.inc.css
+++ b/browser/themes/shared/plugin-doorhanger.inc.css
@@ -22,19 +22,18 @@
   background-repeat: no-repeat;
   width: 16px;
   height: 15px;
   margin-inline-start: 6px;
 }
 
 .click-to-play-plugins-notification-button-container {
   background-color: var(--arrowpanel-dimmed);
-  border-top: 1px solid var(--panel-separator-color);
-  padding: 10px;
   margin-top: 5px;
+  display: flex;
 }
 
 .click-to-play-popup-button {
   width: 50%;
 }
 
 .click-to-play-plugins-notification-description-box {
   padding: 10px;
@@ -44,16 +43,20 @@
   margin-top: 8px;
 }
 
 .click-to-play-plugins-notification-link,
 .center-item-link {
   margin: 0;
 }
 
+.click-to-play-plugins-notification-main-box {
+  width: 100%;
+}
+
 .messageImage[value="plugin-hidden"] {
   list-style-image: url(chrome://browser/skin/notification-icons/plugin.svg);
 }
 
 /* Keep any changes to this style in sync with pluginProblem.css */
 notification.pluginVulnerable {
   background-color: rgb(72,72,72);
   background-image: url(chrome://mozapps/skin/plugins/contentPluginStripe.png);
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1094,16 +1094,19 @@ notification[value="translation"] {
 .private-browsing-indicator {
   display: none;
   pointer-events: none;
 }
 
 #private-browsing-indicator-titlebar {
   display: block;
   position: absolute;
+  /* Need to ensure this gets positioned on top of the position:relative #navigator-toolbox
+   * in case the dark/light themes give that item a background. */
+  z-index: 1;
 }
 
 #main-window[privatebrowsingmode=temporary][tabsintitlebar] #private-browsing-indicator-titlebar > .private-browsing-indicator {
   display: block;
 }
 
 #main-window[privatebrowsingmode=temporary]:-moz-any([inFullscreen],:not([tabsintitlebar])) #TabsToolbar > .private-browsing-indicator {
   display: -moz-box;
--- a/build/clang-plugin/moz.build
+++ b/build/clang-plugin/moz.build
@@ -43,18 +43,18 @@ UNIFIED_SOURCES += [
 
 GENERATED_FILES += ['ThirdPartyPaths.cpp']
 third_party_paths = GENERATED_FILES['ThirdPartyPaths.cpp']
 third_party_paths.script = "ThirdPartyPaths.py:generate"
 third_party_paths.inputs = [
     '/tools/rewriting/ThirdPartyPaths.txt',
 ]
 
-DISABLE_STL_WRAPPING = True
-NO_VISIBILITY_FLAGS = True
+DisableStlWrapping()
+NoVisibilityFlags()
 
 # libc++ is required to build plugins against clang on OS X.
 if CONFIG['HOST_OS_ARCH'] == 'Darwin':
     CXXFLAGS += ['-stdlib=libc++']
     LDFLAGS += ['-lc++']
 
 DIRS += [
     'tests',
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -40,10 +40,10 @@ SOURCES += [
     'TestOverrideBaseCall.cpp',
     'TestOverrideBaseCallAnnotation.cpp',
     'TestRefCountedCopyConstructor.cpp',
     'TestSprintfLiteral.cpp',
     'TestStackClass.cpp',
     'TestTrivialCtorDtor.cpp',
 ]
 
-DISABLE_STL_WRAPPING = True
-NO_VISIBILITY_FLAGS = True
+DisableStlWrapping()
+NoVisibilityFlags()
--- a/build/templates.mozbuild
+++ b/build/templates.mozbuild
@@ -141,11 +141,19 @@ def HostRustLibrary(name, features=None)
     '''Template for host Rust libraries.'''
     HostLibrary(name)
 
     IS_RUST_LIBRARY = True
 
     if features:
         HOST_RUST_LIBRARY_FEATURES = features
 
+@template
+def DisableStlWrapping():
+    COMPILE_FLAGS['STL'] = []
+
+@template
+def NoVisibilityFlags():
+    COMPILE_FLAGS['VISIBILITY'] = []
+
 
 include('gecko_templates.mozbuild')
 include('test_templates.mozbuild')
--- a/build/unix/elfhack/inject/moz.build
+++ b/build/unix/elfhack/inject/moz.build
@@ -18,9 +18,9 @@ else:
     cpu = CONFIG['TARGET_CPU']
 
 SOURCES += [
     "!%s.c" % cpu,
 ]
 
 NO_PGO = True
 
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
--- a/build/unix/stdc++compat/moz.build
+++ b/build/unix/stdc++compat/moz.build
@@ -13,12 +13,12 @@ if CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']:
     HOST_SOURCES += [
         'stdc++compat.cpp',
     ]
 
 FORCE_STATIC_LIB = True
 
 NO_PGO = True
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 DEFINES['MOZ_LIBSTDCXX_VERSION'] = CONFIG['MOZ_LIBSTDCXX_TARGET_VERSION']
 HOST_DEFINES['MOZ_LIBSTDCXX_VERSION'] = CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']
--- a/config/config.mk
+++ b/config/config.mk
@@ -161,34 +161,30 @@ OS_LDFLAGS += $(_DEBUG_LDFLAGS)
 # XXX: What does this? Bug 482434 filed for better explanation.
 ifeq ($(OS_ARCH)_$(GNU_CC),WINNT_)
 ifndef MOZ_DEBUG
 
 # MOZ_DEBUG_SYMBOLS generates debug symbols in separate PDB files.
 # Used for generating an optimized build with debugging symbols.
 # Used in the Windows nightlies to generate symbols for crash reporting.
 ifdef MOZ_DEBUG_SYMBOLS
-ifdef HAVE_64BIT_BUILD
-OS_LDFLAGS += -DEBUG -OPT:REF,ICF
-else
-OS_LDFLAGS += -DEBUG -OPT:REF
-endif
+OS_LDFLAGS += -DEBUG
 endif
 
 #
 # Handle DMD in optimized builds.
 #
 ifdef MOZ_DMD
-ifdef HAVE_64BIT_BUILD
-OS_LDFLAGS = -DEBUG -OPT:REF,ICF
-else
-OS_LDFLAGS = -DEBUG -OPT:REF
-endif
+OS_LDFLAGS = -DEBUG
 endif # MOZ_DMD
 
+ifdef MOZ_OPTIMIZE
+OS_LDFLAGS += -OPT:REF,ICF
+endif # MOZ_OPTIMIZE
+
 endif # MOZ_DEBUG
 
 endif # WINNT && !GNU_CC
 
 #
 # Build using PIC by default
 #
 _ENABLE_PIC=1
@@ -323,18 +319,18 @@ endif # CLANG_CL
 
 # Use warnings-as-errors if ALLOW_COMPILER_WARNINGS is not set to 1 (which
 # includes the case where it's undefined).
 ifneq (1,$(ALLOW_COMPILER_WARNINGS))
 CXXFLAGS += $(WARNINGS_AS_ERRORS)
 CFLAGS   += $(WARNINGS_AS_ERRORS)
 endif # ALLOW_COMPILER_WARNINGS
 
-COMPILE_CFLAGS	= $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CFLAGS) $(_DEPEND_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS) $(MK_COMPILE_DEFINES)
-COMPILE_CXXFLAGS = $(if $(DISABLE_STL_WRAPPING),,$(STL_FLAGS)) $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CXXFLAGS) $(_DEPEND_CFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(MK_COMPILE_DEFINES)
+COMPILE_CFLAGS	= $(COMPUTED_CFLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CFLAGS) $(_DEPEND_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS) $(MK_COMPILE_DEFINES)
+COMPILE_CXXFLAGS = $(COMPUTED_CXXFLAGS) $(DEFINES) $(INCLUDES) $(OS_INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_COMPILE_CXXFLAGS) $(_DEPEND_CFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(MK_COMPILE_DEFINES)
 COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS)
 COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS)
 ASFLAGS += $(MOZBUILD_ASFLAGS)
 
 ifndef CROSS_COMPILE
 HOST_CFLAGS += $(RTL_FLAGS)
 endif
 
--- a/config/external/ffi/moz.build
+++ b/config/external/ffi/moz.build
@@ -5,17 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 FINAL_LIBRARY = 'js'
 
 if CONFIG['MOZ_SYSTEM_FFI']:
     OS_LIBS += CONFIG['MOZ_FFI_LIBS']
 else:
     ALLOW_COMPILER_WARNINGS = True
-    NO_VISIBILITY_FLAGS = True
+    NoVisibilityFlags()
 
     CONFIGURE_DEFINE_FILES += [
         '../../../js/src/ctypes/libffi/fficonfig.h',
     ]
     GENERATED_FILES += [
         '../../../js/src/ctypes/libffi/include/ffi.h',
     ]
     ffi_h = GENERATED_FILES['../../../js/src/ctypes/libffi/include/ffi.h']
--- a/config/external/icu/defs.mozbuild
+++ b/config/external/icu/defs.mozbuild
@@ -28,17 +28,17 @@ if CONFIG['MOZ_DEBUG']:
     DEFINES['U_DEBUG'] = 1
 
 # ICU requires RTTI
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-frtti']
 elif CONFIG['OS_TARGET'] == 'WINNT':
     CXXFLAGS += ['-GR']
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 ALLOW_COMPILER_WARNINGS = True
 
 # We allow compiler warnings, but we can at least cut down on spammy
 # warnings that get triggered for every file.
 if CONFIG['CLANG_CL']:
     CFLAGS += [
         '-Wno-macro-redefined',
         '-Wno-microsoft-include',
--- a/config/moz.build
+++ b/config/moz.build
@@ -5,17 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Build Config')
 
 DIST_INSTALL = False
 # For sanity's sake, we compile nsinstall without the wrapped system
 # headers, so that we can use it to set up the wrapped system headers.
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 
 CONFIGURE_SUBST_FILES += [
     'doxygen.cfg',
     'makefiles/test/Makefile',
     'tests/src-simple/Makefile',
 ]
 
 if CONFIG['HOST_OS_ARCH'] != 'WINNT':
--- a/db/sqlite3/src/moz.build
+++ b/db/sqlite3/src/moz.build
@@ -1,14 +1,14 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 
 EXPORTS += [
     'sqlite3.h',
 ]
 
 # We allow warnings for third-party code that can be updated from upstream.
 ALLOW_COMPILER_WARNINGS = True
 
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -6,16 +6,17 @@
 
 "use strict";
 
 const { interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 const DBG_XUL = "chrome://devtools/content/framework/toolbox-process-window.xul";
 const CHROME_DEBUGGER_PROFILE_NAME = "chrome_debugger_profile";
 
+const { console } = Cu.import("resource://gre/modules/Console.jsm", {});
 const { require, DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Subprocess", "resource://gre/modules/Subprocess.jsm");
 XPCOMUtils.defineLazyGetter(this, "Telemetry", function () {
   return require("devtools/client/shared/telemetry");
 });
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function () {
@@ -271,16 +272,17 @@ BrowserToolboxProcess.prototype = {
     if (!system.constants.MOZILLA_OFFICIAL) {
       args.push("-purgecaches");
     }
 
     this._dbgProcessPromise = Subprocess.call({
       command,
       arguments: args,
       environmentAppend: true,
+      stderr: "stdout",
       environment: {
         // Disable safe mode for the new process in case this was opened via the
         // keyboard shortcut.
         MOZ_DISABLE_SAFE_MODE_KEY: "1",
       },
     }).then(proc => {
       this._dbgProcess = proc;
 
@@ -297,16 +299,18 @@ BrowserToolboxProcess.prototype = {
           data = await pipe.readString();
         }
       };
       dumpPipe(proc.stdout);
 
       proc.wait().then(() => this.close());
 
       return proc;
+    }, err => {
+      console.log(`Error loading Browser Toolbox: ${command} ${args.join(" ")}`, err);
     });
   },
 
   /**
    * Called upon receiving the connectionchange event from a debuggerServer.
    *
    * @param {String} what
    *        Type of connection change (can be either 'opened' or 'closed').
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -7,16 +7,17 @@
 const SOURCE_MAP_WORKER = "resource://devtools/client/shared/source-map/worker.js";
 
 const MAX_ORDINAL = 99;
 const SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsoleEnabled";
 const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
 const DISABLE_AUTOHIDE_PREF = "ui.popup.disable_autohide";
 const HOST_HISTOGRAM = "DEVTOOLS_TOOLBOX_HOST";
 const SCREENSIZE_HISTOGRAM = "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER";
+const CURRENT_THEME_SCALAR = "devtools.current_theme";
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 var {Ci, Cu, Cc} = require("chrome");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var Services = require("Services");
 var {Task} = require("devtools/shared/task");
 var {gDevTools} = require("devtools/client/framework/devtools");
@@ -645,16 +646,21 @@ Toolbox.prototype = {
   },
 
   _pingTelemetry: function () {
     this._telemetry.toolOpened("toolbox");
 
     this._telemetry.logOncePerBrowserVersion(SCREENSIZE_HISTOGRAM,
                                              system.getScreenDimensions());
     this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
+
+    // Log current theme. The question we want to answer is:
+    // "What proportion of users use which themes?"
+    let currentTheme = Services.prefs.getCharPref("devtools.theme");
+    this._telemetry.logKeyedScalar(CURRENT_THEME_SCALAR, currentTheme, 1);
   },
 
   /**
    * Create a simple object to store the state of a toolbox button. The checked state of
    * a button can be updated arbitrarily outside of the scope of the toolbar and its
    * controllers. In order to simplify this interaction this object emits an
    * "updatechecked" event any time the isChecked value is updated, allowing any consuming
    * components to listen and respond to updates.
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -12,16 +12,18 @@
 
 const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
 
 function Telemetry() {
   // Bind pretty much all functions so that callers do not need to.
   this.toolOpened = this.toolOpened.bind(this);
   this.toolClosed = this.toolClosed.bind(this);
   this.log = this.log.bind(this);
+  this.logScalar = this.logScalar.bind(this);
+  this.logKeyedScalar = this.logKeyedScalar.bind(this);
   this.logOncePerBrowserVersion = this.logOncePerBrowserVersion.bind(this);
   this.destroy = this.destroy.bind(this);
 
   this._timers = new Map();
 }
 
 module.exports = Telemetry;
 
@@ -305,16 +307,46 @@ Telemetry.prototype = {
       Services.telemetry.scalarSet(scalarId, value);
     } catch (e) {
       dump(`Warning: An attempt was made to write to the ${scalarId} ` +
            `scalar, which is not defined in Scalars.yaml\n`);
     }
   },
 
   /**
+   * Log a value to a keyed count scalar.
+   *
+   * @param  {String} scalarId
+   *         Scalar in which the data is to be stored.
+   * @param  {String} key
+   *         The key within the  scalar.
+   * @param  value
+   *         Value to store.
+   */
+  logKeyedScalar: function (scalarId, key, value) {
+    if (!scalarId) {
+      return;
+    }
+
+    try {
+      if (isNaN(value)) {
+        dump(`Warning: An attempt was made to write a non-numeric value ` +
+             `${value} to the ${scalarId} scalar. Only numeric values are ` +
+             `allowed.`);
+
+        return;
+      }
+      Services.telemetry.keyedScalarAdd(scalarId, key, value);
+    } catch (e) {
+      dump(`Warning: An attempt was made to write to the ${scalarId} ` +
+           `scalar, which is not defined in Scalars.yaml\n`);
+    }
+  },
+
+  /**
    * Log a value to a keyed histogram.
    *
    * @param  {String} histogramId
    *         Histogram in which the data is to be stored.
    * @param  {String} key
    *         The key within the single histogram.
    * @param  [value]
    *         Optional value to store.
--- a/devtools/server/tests/unit/test_objectgrips-17.js
+++ b/devtools/server/tests/unit/test_objectgrips-17.js
@@ -101,20 +101,16 @@ function testPrincipal(principal, wantXr
     // kinds of security wrappers, or no wrapper at all.
     // To detect that no proxy trap runs, the proxy handler should define all possible
     // traps, but the list is long and may change. Therefore a second proxy is used as
     // the handler, so that a single `get` trap suffices.
     gGlobal.eval(`
       var trapDidRun = false;
       var proxy = new Proxy({}, new Proxy({}, {get: (_, trap) => {
         return function(_, arg) {
-          if (trap === "has" && arg === "__exposedProps__") {
-            // Tolerate this case until bug 1392026 is fixed.
-            return false;
-          }
           trapDidRun = true;
           throw new Error("proxy trap '" + trap + "' was called.");
         }
       }}));
       var inheritsProxy = Object.create(proxy, {x:{value:1}});
     `);
     let data = Cu.createObjectIn(gDebuggee, {defineAs: "data"});
     data.proxy = gGlobal.proxy;
--- a/dom/animation/AnimationEffectReadOnly.cpp
+++ b/dom/animation/AnimationEffectReadOnly.cpp
@@ -181,18 +181,19 @@ AnimationEffectReadOnly::GetComputedTimi
   // Factor in iteration start offset.
   if (IsFinite(overallProgress)) {
     overallProgress += result.mIterationStart;
   }
 
   // Determine the 0-based index of the current iteration.
   // https://w3c.github.io/web-animations/#current-iteration
   result.mCurrentIteration =
-    IsInfinite(result.mIterations) &&
-      result.mPhase == ComputedTiming::AnimationPhase::After
+    (result.mIterations >= UINT64_MAX
+     && result.mPhase == ComputedTiming::AnimationPhase::After)
+    || overallProgress >= UINT64_MAX
     ? UINT64_MAX // In GetComputedTimingDictionary(),
                  // we will convert this into Infinity
     : static_cast<uint64_t>(overallProgress);
 
   // Convert the overall progress to a fraction of a single iteration--the
   // simply iteration progress.
   // https://w3c.github.io/web-animations/#simple-iteration-progress
   double progress = IsFinite(overallProgress)
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/crashtests/1334582-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = function(){
+  let a = document.documentElement.animate([], {"iterations": 1.7976931348623157e+308, "fill": "both"});
+};
+</script>
+</head>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/crashtests/1334582-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = function(){
+  let a = document.documentElement.animate([], {"iterationStart": 1.7976931348623157e+308, "fill": "both"});
+};
+</script>
+</head>
+</html>
--- a/dom/animation/test/crashtests/crashtests.list
+++ b/dom/animation/test/crashtests/crashtests.list
@@ -18,15 +18,17 @@ pref(dom.animations-api.core.enabled,tru
 pref(dom.animations-api.core.enabled,true) load 1323114-2.html
 pref(dom.animations-api.core.enabled,true) load 1324554-1.html
 pref(dom.animations-api.core.enabled,true) load 1325193-1.html
 pref(dom.animations-api.core.enabled,true) load 1330190-1.html
 pref(dom.animations-api.core.enabled,true) load 1330190-2.html
 pref(dom.animations-api.core.enabled,true) load 1330513-1.html
 pref(dom.animations-api.core.enabled,true) load 1333539-1.html
 pref(dom.animations-api.core.enabled,true) load 1333539-2.html
+pref(dom.animations-api.core.enabled,true) load 1334582-1.html
+pref(dom.animations-api.core.enabled,true) load 1334582-2.html
 pref(dom.animations-api.core.enabled,true) load 1334583-1.html
 pref(dom.animations-api.core.enabled,true) load 1335998-1.html
 pref(dom.animations-api.core.enabled,true) load 1343589-1.html
 pref(dom.animations-api.core.enabled,true) load 1359658-1.html
 pref(dom.animations-api.core.enabled,true) load 1373712-1.html
 pref(dom.animations-api.core.enabled,true) load 1379606-1.html
 pref(dom.animations-api.core.enabled,true) load 1393605-1.html
--- a/dom/base/DOMParser.cpp
+++ b/dom/base/DOMParser.cpp
@@ -458,17 +458,17 @@ DOMParser::SetUpDocument(DocumentFlavor 
 
     nsCOMPtr<nsIPrincipal> prin = NullPrincipal::Create();
     rv = Init(prin, nullptr, nullptr, scriptHandlingObject);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Try to inherit a style backend.
   auto styleBackend = StyleBackendType::None;
-  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mScriptHandlingObject);
+  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(scriptHandlingObject);
   if (window && window->GetExtantDoc()) {
     styleBackend = window->GetExtantDoc()->GetStyleBackendType();
   }
 
   NS_ASSERTION(mPrincipal, "Must have principal by now");
   NS_ASSERTION(mDocumentURI, "Must have document URI by now");
 
   return NS_NewDOMDocument(aResult, EmptyString(), EmptyString(), nullptr,
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -16,17 +16,16 @@ DEPRECATED_OPERATION(GetAttributeNodeNS)
 DEPRECATED_OPERATION(SetAttributeNodeNS)
 DEPRECATED_OPERATION(RemoveAttributeNode)
 DEPRECATED_OPERATION(CreateAttribute)
 DEPRECATED_OPERATION(CreateAttributeNS)
 DEPRECATED_OPERATION(NodeValue)
 DEPRECATED_OPERATION(TextContent)
 DEPRECATED_OPERATION(EnablePrivilege)
 DEPRECATED_OPERATION(DOMExceptionCode)
-DEPRECATED_OPERATION(NoExposedProps)
 DEPRECATED_OPERATION(MutationEvent)
 DEPRECATED_OPERATION(Components)
 DEPRECATED_OPERATION(PrefixedVisibilityAPI)
 DEPRECATED_OPERATION(NodeIteratorDetach)
 DEPRECATED_OPERATION(LenientThis)
 DEPRECATED_OPERATION(GetPreventDefault)
 DEPRECATED_OPERATION(GetSetUserData)
 DEPRECATED_OPERATION(MozGetAsFile)
--- a/dom/base/test/chrome/cpows_child.js
+++ b/dom/base/test/chrome/cpows_child.js
@@ -100,17 +100,23 @@ function parent_test(finish)
 
     let result = check_func(10);
     ok(result == 20, "calling function in parent worked");
     return result;
   }
 
   addMessageListener("cpows:from_parent", (msg) => {
     let obj = msg.objects.obj;
-    ok(obj.a == 1, "correct value from parent");
+    if (is_remote) {
+      ok(obj.a == undefined, "__exposedProps__ should not work");
+    } else {
+      // The same process test is not run as content, so the field can
+      // be accessed even though __exposedProps__ has been removed.
+      ok(obj.a == 1, "correct value from parent");
+    }
 
     // Test that a CPOW reference to a function in the chrome process
     // is callable from unprivileged content. Greasemonkey uses this
     // functionality.
     let func = msg.objects.func;
     let sb = Cu.Sandbox('http://www.example.com', {});
     sb.func = func;
     ok(sb.eval('func()') == 101, "can call parent's function in child");
@@ -255,21 +261,21 @@ function lifetime_test(finish)
     finish();
     return;
   }
 
   dump("beginning lifetime test\n");
   var obj = {"will_die": {"f": 1}};
   let [result] = sendRpcMessage("cpows:lifetime_test_1", {}, {obj: obj});
   ok(result == 10, "got sync result");
-  ok(obj.wont_die.f == 2, "got reverse CPOW");
+  ok(obj.wont_die.f == undefined, "got reverse CPOW");
   obj.will_die = null;
   Components.utils.schedulePreciseGC(function() {
     addMessageListener("cpows:lifetime_test_3", (msg) => {
-      ok(obj.wont_die.f == 2, "reverse CPOW still works");
+      ok(obj.wont_die.f == undefined, "reverse CPOW still works");
       finish();
     });
     sendRpcMessage("cpows:lifetime_test_2");
   });
 }
 
 function cancel_test(finish)
 {
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -3179,17 +3179,17 @@ nsresult MigrateFrom25To26(mozIStorageCo
   // Add the response_padding_size column.
   // Note: only opaque repsonse should be non-null interger.
   nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "ALTER TABLE entries "
     "ADD COLUMN response_padding_size INTEGER NULL "
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+  rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     "UPDATE entries SET response_padding_size = 0 "
       "WHERE response_type = 4" // opaque response
   ));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = aConn->SetSchemaVersion(26);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
--- a/dom/cache/FileUtils.cpp
+++ b/dom/cache/FileUtils.cpp
@@ -781,18 +781,20 @@ LockedUpdateDirectoryPaddingFile(nsIFile
                                  const bool aTemporaryFileExist)
 {
   MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
   MOZ_DIAGNOSTIC_ASSERT(aConn);
   MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
   MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
 
   int64_t currentPaddingSize = 0;
-  nsresult rv = LockedDirectoryPaddingGet(aBaseDir, &currentPaddingSize);
-  if (NS_WARN_IF(NS_FAILED(rv)) || aTemporaryFileExist) {
+  nsresult rv = NS_OK;
+  if (aTemporaryFileExist ||
+      NS_WARN_IF(NS_FAILED(rv =
+        LockedDirectoryPaddingGet(aBaseDir, &currentPaddingSize)))) {
     // Fail to read padding size from the dir padding file, so try to restore.
     if (rv != NS_ERROR_FILE_NOT_FOUND &&
         rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
       // Not delete the temporary padding file here, because we're going to
       // overwrite it below anyway.
       rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE);
       if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
     }
--- a/dom/cache/test/xpcshell/head.js
+++ b/dom/cache/test/xpcshell/head.js
@@ -14,17 +14,17 @@ var Cu = Components.utils;
 var ss = Cc['@mozilla.org/storage/service;1']
          .createInstance(Ci.mozIStorageService);
 var sts = Cc['@mozilla.org/network/stream-transport-service;1']
           .getService(Ci.nsIStreamTransportService);
 var hash = Cc['@mozilla.org/security/hash;1']
            .createInstance(Ci.nsICryptoHash);
 
 // Expose Cache and Fetch symbols on the global
-Cu.importGlobalProperties(['caches', 'fetch']);
+Cu.importGlobalProperties(['caches', 'File', 'fetch']);
 
 // Extract a zip file into the profile
 function create_test_profile(zipFileName) {
   do_get_profile();
 
   var directoryService = Cc['@mozilla.org/file/directory_service;1']
                          .getService(Ci.nsIProperties);
   var profileDir = directoryService.get('ProfD', Ci.nsIFile);
@@ -70,8 +70,24 @@ function create_test_profile(zipFileName
 
       istream.close();
       bostream.close();
     }
   }
 
   zipReader.close();
 }
+
+function getCacheDir()
+{
+  let dirService = Cc["@mozilla.org/file/directory_service;1"]
+                   .getService(Ci.nsIProperties);
+
+  let profileDir = dirService.get("ProfD", Ci.nsIFile);
+  let cacheDir = profileDir.clone();
+  cacheDir.append("storage");
+  cacheDir.append("default");
+  cacheDir.append("chrome");
+  cacheDir.append("cache");
+
+  return cacheDir;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_padding_error_handle.js
@@ -0,0 +1,65 @@
+/**
+ *  This test is mainly to verify cache actions work as usual even there exists
+ *  an unexpected padding file.
+ */
+
+function getTempPaddingFilePath() {
+  let cacheDir = getCacheDir();
+  let temporaryPaddingFile = cacheDir.clone();
+  temporaryPaddingFile.append(".padding-tmp");
+  return temporaryPaddingFile;
+}
+
+function createTempPaddingFile () {
+  let temporaryPaddingFile = getTempPaddingFilePath();
+  temporaryPaddingFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+
+  ok(temporaryPaddingFile.exists(),
+     "Temporary padding file does be created by test");
+}
+
+async function run_test() {
+  do_test_pending();
+  create_test_profile('schema_25_profile.zip');
+  let cache = await caches.open("test");
+
+  // Step 1: Verify cache.match won't fail when there is a temporary padding
+  // file
+  createTempPaddingFile();
+
+  let response = await cache.match("https://www.mozilla.org");
+  ok(!!response, "Upgrade from 25 to 26 do succeed");
+
+  // Note: Only cache write actions(e.g. cache.put/add/addAll/delete) will
+  // remove unexpected temporary padding file when writting an opaque response
+  // into the file-system. Cache read actions(e.g. cache.keys/match) won't.
+  let temporaryPaddingFile = getTempPaddingFilePath();
+  ok(temporaryPaddingFile.exists(),
+     "Temporary padding file doesn't be removed by cache.match");
+
+  // Step 2: Verify cache.put won't fail when there is a temporary padding
+  // file
+  await cache.put("https://foo.com", response);
+  ok(!temporaryPaddingFile.exists(),
+     "Temporary padding file does be removed by cache.put");
+
+  // Step 3: Verify cache.keys won't fail when there is a temporary padding
+  // file
+  createTempPaddingFile();
+
+  let cacheEntries = await cache.keys("https://foo.com");
+  ok(cacheEntries.length === 1, "Cache.put does succeed");
+
+  ok(temporaryPaddingFile.exists(),
+     "Temporary padding file doesn't be removed by cache.keys");
+
+  // Step 4: Verify cache.delete won't fail when there is a temporary padding
+  // file
+  await cache.delete("https://foo.com");
+  ok(!temporaryPaddingFile.exists(),
+     "Temporary padding file does be removed by cache.delete");
+
+  await caches.delete("test");
+
+  do_test_finished();
+}
--- a/dom/cache/test/xpcshell/xpcshell.ini
+++ b/dom/cache/test/xpcshell/xpcshell.ini
@@ -8,9 +8,10 @@ support-files =
   schema_15_profile.zip
   schema_25_profile.zip
 
 # dummy test entry to generate profile zip files
 [make_profile.js]
   skip-if = true
 
 [test_migration.js]
+[test_padding_error_handle.js]
 [test_schema_26_upgrade.js]
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1147,17 +1147,17 @@ HTMLInputElement::HTMLInputElement(alrea
   , mNumberControlSpinnerIsSpinning(false)
   , mNumberControlSpinnerSpinsUp(false)
   , mPickerRunning(false)
   , mSelectionCached(true)
   , mIsPreviewEnabled(false)
   , mHasPatternAttribute(false)
 {
   // If size is above 512, mozjemalloc allocates 1kB, see
-  // memory/mozjemalloc/jemalloc.c
+  // memory/build/mozjemalloc.cpp
   static_assert(sizeof(HTMLInputElement) <= 512,
                 "Keep the size of HTMLInputElement under 512 to avoid "
                 "performance regression!");
 
   // We are in a type=text so we now we currenty need a nsTextEditorState.
   mInputData.mState =
     nsTextEditorState::Construct(this, &sCachedTextEditorState);
 
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -149,18 +149,16 @@ MediaStreamStopDeprecatedWarning=MediaSt
 # LOCALIZATION NOTE: %S is the URL of the web page which is not served on HTTPS and thus is not encrypted and considered insecure.
 MediaEMEInsecureContextDeprecatedWarning=Using Encrypted Media Extensions at %S on an insecure (i.e. non-HTTPS) context is deprecated and will soon be removed. You should consider switching to a secure origin such as HTTPS.
 # LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (either an audioCapabilities or a videoCapabilities) that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
 MediaEMENoCapabilitiesDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) without passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities is deprecated and will soon become unsupported.
 # LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (a "codecs" string in the "contentType") that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
 MediaEMENoCodecsDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities without a contentType with a “codecs” string is deprecated and will soon become unsupported.
 # LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
 DOMExceptionCodeWarning=Use of DOMException’s code attribute is deprecated. Use name instead.
-# LOCALIZATION NOTE: Do not translate "__exposedProps__"
-NoExposedPropsWarning=Exposing chrome JS objects to content without __exposedProps__ is insecure and deprecated. See https://developer.mozilla.org/en/XPConnect_wrappers for more information.
 # LOCALIZATION NOTE: Do not translate "Mutation Event" and "MutationObserver"
 MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver instead.
 # LOCALIZATION NOTE: Do not translate "Components"
 ComponentsWarning=The Components object is deprecated. It will soon be removed.
 PluginHangUITitle=Warning: Unresponsive plugin
 PluginHangUIMessage=%S may be busy, or it may have stopped responding. You can stop the plugin now, or you can continue to see if the plugin will complete.
 PluginHangUIWaitButton=Continue
 PluginHangUIStopButton=Stop plugin
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -170,17 +170,17 @@ public:
 
   // Main thread only.
   virtual void OnResolveLoadSessionPromise(uint32_t aPromiseId,
                                            bool aSuccess) = 0;
 
   // Main thread only.
   virtual void OnSessionMessage(const nsAString& aSessionId,
                                 dom::MediaKeyMessageType aMessageType,
-                                nsTArray<uint8_t>& aMessage) = 0;
+                                const nsTArray<uint8_t>& aMessage) = 0;
 
   // Main thread only.
   virtual void OnExpirationChange(const nsAString& aSessionId,
                                   UnixTime aExpiryTime) = 0;
 
   // Main thread only.
   virtual void OnSessionClosed(const nsAString& aSessionId) = 0;
 
--- a/dom/media/eme/EMEUtils.cpp
+++ b/dom/media/eme/EMEUtils.cpp
@@ -67,20 +67,9 @@ KeySystemToGMPName(const nsAString& aKey
   }
   if (IsWidevineKeySystem(aKeySystem)) {
     return NS_LITERAL_STRING("gmp-widevinecdm");
   }
   MOZ_ASSERT(false, "We should only call this for known GMPs");
   return EmptyString();
 }
 
-CDMType
-ToCDMTypeTelemetryEnum(const nsString& aKeySystem)
-{
-  if (IsWidevineKeySystem(aKeySystem)) {
-    return CDMType::eWidevine;
-  } else if (IsClearkeyKeySystem(aKeySystem)) {
-    return CDMType::eClearKey;
-  }
-  return CDMType::eUnknown;
-}
-
 } // namespace mozilla
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -447,18 +447,16 @@ MediaKeys::OnCDMCreated(PromiseId aId, c
   promise->MaybeResolve(keys);
   if (mCreatePromiseId == aId) {
     Release();
   }
 
   MediaKeySystemAccess::NotifyObservers(mParent,
                                         mKeySystem,
                                         MediaKeySystemStatus::Cdm_created);
-
-  Telemetry::Accumulate(Telemetry::VIDEO_CDM_CREATED, ToCDMTypeTelemetryEnum(mKeySystem));
 }
 
 static bool
 IsSessionTypeSupported(const MediaKeySessionType aSessionType,
                        const MediaKeySystemConfiguration& aConfig)
 {
   if (aSessionType == MediaKeySessionType::Temporary) {
     // Temporary is always supported.
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -221,17 +221,17 @@ MediaDrmCDMProxy::OnResolveLoadSessionPr
     return;
   }
   mKeys->OnSessionLoaded(aPromiseId, aSuccess);
 }
 
 void
 MediaDrmCDMProxy::OnSessionMessage(const nsAString& aSessionId,
                                    dom::MediaKeyMessageType aMessageType,
-                                   nsTArray<uint8_t>& aMessage)
+                                   const nsTArray<uint8_t>& aMessage)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
   }
   RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
   if (session) {
     session->DispatchKeyMessage(aMessageType, aMessage);
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
@@ -72,17 +72,17 @@ public:
 
   void OnSetSessionId(uint32_t aCreateSessionToken,
                       const nsAString& aSessionId) override;
 
   void OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) override;
 
   void OnSessionMessage(const nsAString& aSessionId,
                         dom::MediaKeyMessageType aMessageType,
-                        nsTArray<uint8_t>& aMessage) override;
+                        const nsTArray<uint8_t>& aMessage) override;
 
   void OnExpirationChange(const nsAString& aSessionId,
                           UnixTime aExpiryTime) override;
 
   void OnSessionClosed(const nsAString& aSessionId) override;
 
   void OnSessionError(const nsAString& aSessionId,
                       nsresult aException,
--- a/dom/media/gmp-plugin-openh264/moz.build
+++ b/dom/media/gmp-plugin-openh264/moz.build
@@ -20,12 +20,12 @@ SOURCES += [
 SharedLibrary("fakeopenh264")
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
         'ole32',
     ]
 
 USE_STATIC_LIBS = True
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 # Don't use STL wrappers; this isn't Gecko code
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 NO_PGO = True
--- a/dom/media/gmp-plugin/moz.build
+++ b/dom/media/gmp-plugin/moz.build
@@ -22,12 +22,12 @@ DEFINES['GMP_FAKE_SUPPORT_DECRYPT'] = Tr
 SharedLibrary("fake")
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
         'ole32',
     ]
 
 USE_STATIC_LIBS = True
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 # Don't use STL wrappers; this isn't Gecko code
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 NO_PGO = True
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMCallback.h
@@ -0,0 +1,52 @@
+/* -*- 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 ChromiumCDMCallback_h_
+#define ChromiumCDMCallback_h_
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/MediaKeyMessageEventBinding.h" // For MediaKeyMessageType
+#include "mozilla/gmp/GMPTypes.h" // For CDMKeyInformation
+
+class ChromiumCDMCallback {
+public:
+
+  virtual ~ChromiumCDMCallback() {}
+
+  virtual void SetSessionId(uint32_t aPromiseId,
+                            const nsCString& aSessionId) = 0;
+
+  virtual void ResolveLoadSessionPromise(uint32_t aPromiseId,
+                                         bool aSuccessful) = 0;
+
+  virtual void ResolvePromise(uint32_t aPromiseId) = 0;
+
+  virtual void RejectPromise(uint32_t aPromiseId,
+                             nsresult aError,
+                             const nsCString& aErrorMessage) = 0;
+
+  virtual void SessionMessage(const nsACString& aSessionId,
+                              uint32_t aMessageType,
+                              nsTArray<uint8_t>&& aMessage) = 0;
+
+  virtual void SessionKeysChange(const nsCString& aSessionId,
+                                 nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) = 0;
+
+  virtual void ExpirationChange(const nsCString& aSessionId,
+                                double aSecondsSinceEpoch) = 0;
+
+  virtual void SessionClosed(const nsCString& aSessionId) = 0;
+
+  virtual void LegacySessionError(const nsCString& aSessionId,
+                                  nsresult aError,
+                                  uint32_t aSystemCode,
+                                  const nsCString& aMessage) = 0;
+  virtual void Terminated() = 0;
+
+  virtual void Shutdown() = 0;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMCallbackProxy.cpp
@@ -0,0 +1,188 @@
+/* -*- 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 "ChromiumCDMCallbackProxy.h"
+
+#include "ChromiumCDMProxy.h"
+#include "content_decryption_module.h"
+
+namespace mozilla {
+
+template<class Func, class... Args>
+void ChromiumCDMCallbackProxy::DispatchToMainThread(const char* const aLabel,
+                                                    Func aFunc,
+                                                    Args&&... aArgs)
+{
+  mMainThread->Dispatch(
+    // Use Decay to ensure all the types are passed by value not by reference.
+    NewRunnableMethod<typename Decay<Args>::Type...>(
+      aLabel,
+      mProxy,
+      aFunc,
+      Forward<Args>(aArgs)...),
+    NS_DISPATCH_NORMAL);
+}
+
+void
+ChromiumCDMCallbackProxy::SetSessionId(uint32_t aPromiseId,
+                                       const nsCString& aSessionId)
+{
+  DispatchToMainThread("ChromiumCDMProxy::OnSetSessionId",
+                       &ChromiumCDMProxy::OnSetSessionId,
+                       aPromiseId,
+                       NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void
+ChromiumCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+                                                    bool aSuccessful)
+{
+  DispatchToMainThread("ChromiumCDMProxy::OnResolveLoadSessionPromise",
+                       &ChromiumCDMProxy::OnResolveLoadSessionPromise,
+                       aPromiseId,
+                       aSuccessful);
+}
+
+void
+ChromiumCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+{
+  DispatchToMainThread("ChromiumCDMProxy::ResolvePromise",
+                       &ChromiumCDMProxy::ResolvePromise,
+                       aPromiseId);
+}
+
+void
+ChromiumCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+                                        nsresult aError,
+                                        const nsCString& aErrorMessage)
+{
+  DispatchToMainThread("ChromiumCDMProxy::RejectPromise",
+                       &ChromiumCDMProxy::RejectPromise,
+                       aPromiseId,
+                       aError,
+                       aErrorMessage);
+}
+
+
+static dom::MediaKeyMessageType
+ToDOMMessageType(uint32_t aMessageType)
+{
+  switch (static_cast<cdm::MessageType>(aMessageType)) {
+    case cdm::kLicenseRequest:
+      return dom::MediaKeyMessageType::License_request;
+    case cdm::kLicenseRenewal:
+      return dom::MediaKeyMessageType::License_renewal;
+    case cdm::kLicenseRelease:
+      return dom::MediaKeyMessageType::License_release;
+  }
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value.");
+  return dom::MediaKeyMessageType::License_request;
+}
+
+void
+ChromiumCDMCallbackProxy::SessionMessage(const nsACString& aSessionId,
+                                         uint32_t aMessageType,
+                                         nsTArray<uint8_t>&& aMessage)
+{
+  DispatchToMainThread("ChromiumCDMProxy::OnSessionMessage",
+                       &ChromiumCDMProxy::OnSessionMessage,
+                       NS_ConvertUTF8toUTF16(aSessionId),
+                       ToDOMMessageType(aMessageType),
+                       Move(aMessage));
+}
+
+static dom::MediaKeyStatus
+ToDOMMediaKeyStatus(uint32_t aStatus)
+{
+  switch (static_cast<cdm::KeyStatus>(aStatus)) {
+    case cdm::kUsable:
+      return dom::MediaKeyStatus::Usable;
+    case cdm::kInternalError:
+      return dom::MediaKeyStatus::Internal_error;
+    case cdm::kExpired:
+      return dom::MediaKeyStatus::Expired;
+    case cdm::kOutputRestricted:
+      return dom::MediaKeyStatus::Output_restricted;
+    case cdm::kOutputDownscaled:
+      return dom::MediaKeyStatus::Output_downscaled;
+    case cdm::kStatusPending:
+      return dom::MediaKeyStatus::Status_pending;
+    case cdm::kReleased:
+      return dom::MediaKeyStatus::Released;
+  }
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value.");
+  return dom::MediaKeyStatus::Internal_error;
+}
+
+void
+ChromiumCDMCallbackProxy::SessionKeysChange(const nsCString& aSessionId,
+                                            nsTArray<mozilla::gmp::CDMKeyInformation> && aKeysInfo)
+{
+  bool keyStatusesChange = false;
+  {
+    CDMCaps::AutoLock caps(mProxy->Capabilites());
+    for (const auto& keyInfo : aKeysInfo) {
+      keyStatusesChange |=
+        caps.SetKeyStatus(keyInfo.mKeyId(),
+                          NS_ConvertUTF8toUTF16(aSessionId),
+                          dom::Optional<dom::MediaKeyStatus>(
+                            ToDOMMediaKeyStatus(keyInfo.mStatus())));
+    }
+  }
+  if (keyStatusesChange) {
+    DispatchToMainThread("ChromiumCDMProxy::OnKeyStatusesChange",
+                         &ChromiumCDMProxy::OnKeyStatusesChange,
+                         NS_ConvertUTF8toUTF16(aSessionId));
+  }
+}
+
+void
+ChromiumCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+                                           double aSecondsSinceEpoch)
+{
+  DispatchToMainThread("ChromiumCDMProxy::OnExpirationChange",
+                       &ChromiumCDMProxy::OnExpirationChange,
+                       NS_ConvertUTF8toUTF16(aSessionId),
+                       UnixTime(aSecondsSinceEpoch * 1000));
+
+}
+
+void
+ChromiumCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+{
+  DispatchToMainThread("ChromiumCDMProxy::OnSessionClosed",
+                       &ChromiumCDMProxy::OnSessionClosed ,
+                       NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void
+ChromiumCDMCallbackProxy::LegacySessionError(const nsCString& aSessionId,
+                                             nsresult aError,
+                                             uint32_t aSystemCode,
+                                             const nsCString& aMessage)
+{
+  DispatchToMainThread("ChromiumCDMProxy::OnSessionError",
+                       &ChromiumCDMProxy::OnSessionError ,
+                       NS_ConvertUTF8toUTF16(aSessionId),
+                       aError,
+                       aSystemCode,
+                       NS_ConvertUTF8toUTF16(aMessage));
+}
+
+void
+ChromiumCDMCallbackProxy::Terminated()
+{
+  DispatchToMainThread("ChromiumCDMProxy::Terminated",
+                       &ChromiumCDMProxy::Terminated);
+}
+
+void
+ChromiumCDMCallbackProxy::Shutdown()
+{
+  DispatchToMainThread("ChromiumCDMProxy::Shutdown",
+                       &ChromiumCDMProxy::Shutdown);
+}
+
+} //namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMCallbackProxy.h
@@ -0,0 +1,68 @@
+/* -*- 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 ChromiumCDMCallbackProxy_h_
+#define ChromiumCDMCallbackProxy_h_
+
+#include "ChromiumCDMCallback.h"
+#include "ChromiumCDMProxy.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class ChromiumCDMCallbackProxy : public ChromiumCDMCallback {
+public:
+
+  ChromiumCDMCallbackProxy(ChromiumCDMProxy* aProxy,
+                           nsIEventTarget* aMainThread)
+    : mProxy(aProxy), mMainThread(aMainThread)
+  {
+  }
+
+  void SetSessionId(uint32_t aPromiseId,
+                    const nsCString& aSessionId) override;
+
+  void ResolveLoadSessionPromise(uint32_t aPromiseId,
+                                 bool aSuccessful) override;
+
+  void ResolvePromise(uint32_t aPromiseId) override;
+
+  void RejectPromise(uint32_t aPromiseId,
+                     nsresult aError,
+                     const nsCString& aErrorMessage) override;
+
+  void SessionMessage(const nsACString& aSessionId,
+                      uint32_t aMessageType,
+                      nsTArray<uint8_t>&& aMessage) override;
+
+  void SessionKeysChange(const nsCString& aSessionId,
+                         nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) override;
+
+  void ExpirationChange(const nsCString& aSessionId,
+                        double aSecondsSinceEpoch) override;
+
+  void SessionClosed(const nsCString& aSessionId) override;
+
+  void LegacySessionError(const nsCString& aSessionId,
+                          nsresult aError,
+                          uint32_t aSystemCode,
+                          const nsCString& aMessage) override;
+  void Terminated() override;
+
+  void Shutdown() override;
+
+private:
+  template<class Func, class... Args>
+  void DispatchToMainThread(const char* const aLabel,
+                            Func aFunc,
+                            Args&&... aArgs);
+  // Warning: Weak ref.
+  ChromiumCDMProxy* mProxy;
+  const nsCOMPtr<nsIEventTarget> mMainThread;
+
+};
+
+} //namespace mozilla
+#endif
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -1,15 +1,17 @@
 /* -*- 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 "ChromiumCDMParent.h"
 
+#include "ChromiumCDMCallback.h"
+#include "ChromiumCDMCallbackProxy.h"
 #include "ChromiumCDMProxy.h"
 #include "content_decryption_module.h"
 #include "GMPContentChild.h"
 #include "GMPContentParent.h"
 #include "GMPLog.h"
 #include "GMPUtils.h"
 #include "MediaPrefs.h"
 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
@@ -35,26 +37,26 @@ ChromiumCDMParent::ChromiumCDMParent(GMP
   GMP_LOG(
     "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, id=%u)",
     this,
     aContentParent,
     aPluginId);
 }
 
 bool
-ChromiumCDMParent::Init(ChromiumCDMProxy* aProxy,
+ChromiumCDMParent::Init(ChromiumCDMCallback* aCDMCallback,
                         bool aAllowDistinctiveIdentifier,
                         bool aAllowPersistentState,
                         nsIEventTarget* aMainThread)
 {
   GMP_LOG("ChromiumCDMParent::Init(this=%p)", this);
-  if (!aProxy || !aMainThread) {
+  if (!aCDMCallback || !aMainThread) {
     return false;
   }
-  mProxy = aProxy;
+  mCDMCallback = aCDMCallback;
   mMainThread = aMainThread;
   return SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState);
 }
 
 void
 ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
                                  uint32_t aSessionType,
                                  uint32_t aInitDataType,
@@ -274,92 +276,91 @@ ipc::IPCResult
 ChromiumCDMParent::RecvOnResolveNewSessionPromise(const uint32_t& aPromiseId,
                                                   const nsCString& aSessionId)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%u, "
           "sid=%s)",
           this,
           aPromiseId,
           aSessionId.get());
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return IPC_OK();
   }
 
   Maybe<uint32_t> token = mPromiseToCreateSessionToken.GetAndRemove(aPromiseId);
   if (token.isNothing()) {
     RejectPromise(aPromiseId,
                   NS_ERROR_DOM_INVALID_STATE_ERR,
                   NS_LITERAL_CSTRING("Lost session token for new session."));
     return IPC_OK();
   }
 
-  mMainThread->Dispatch(
-    NewRunnableMethod<uint32_t, nsString>("ChromiumCDMProxy::OnSetSessionId",
-                                          mProxy,
-                                          &ChromiumCDMProxy::OnSetSessionId,
-                                          token.value(),
-                                          NS_ConvertUTF8toUTF16(aSessionId)),
-    NS_DISPATCH_NORMAL);
+  mCDMCallback->SetSessionId(token.value(), aSessionId);
 
   ResolvePromise(aPromiseId);
 
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
                                                  const bool& aSuccessful)
 {
   GMP_LOG("ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%u, "
           "successful=%d)",
           this,
           aPromiseId,
           aSuccessful);
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return IPC_OK();
   }
 
-  mMainThread->Dispatch(
-    NewRunnableMethod<uint32_t, bool>(
-      "ChromiumCDMProxy::OnResolveLoadSessionPromise",
-      mProxy,
-      &ChromiumCDMProxy::OnResolveLoadSessionPromise,
-      aPromiseId,
-      aSuccessful),
-    NS_DISPATCH_NORMAL);
+  mCDMCallback->ResolveLoadSessionPromise(aPromiseId, aSuccessful);
 
   return IPC_OK();
 }
+
 void
 ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId)
 {
   GMP_LOG(
     "ChromiumCDMParent::ResolvePromise(this=%p, pid=%u)", this, aPromiseId);
 
   // Note: The MediaKeys rejects all pending DOM promises when it
   // initiates shutdown.
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return;
   }
 
-  mMainThread->Dispatch(
-    NewRunnableMethod<uint32_t>("ChromiumCDMProxy::ResolvePromise",
-                                mProxy,
-                                &ChromiumCDMProxy::ResolvePromise,
-                                aPromiseId),
-    NS_DISPATCH_NORMAL);
+  mCDMCallback->ResolvePromise(aPromiseId);
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnResolvePromise(const uint32_t& aPromiseId)
 {
   ResolvePromise(aPromiseId);
   return IPC_OK();
 }
 
+void
+ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
+                                 nsresult aError,
+                                 const nsCString& aErrorMessage)
+{
+  GMP_LOG(
+    "ChromiumCDMParent::RejectPromise(this=%p, pid=%u)", this, aPromiseId);
+  // Note: The MediaKeys rejects all pending DOM promises when it
+  // initiates shutdown.
+  if (!mCDMCallback || mIsShutdown) {
+    return;
+  }
+
+  mCDMCallback->RejectPromise(aPromiseId, aError, aErrorMessage);
+}
+
 static nsresult
 ToNsresult(uint32_t aError)
 {
   switch (static_cast<cdm::Error>(aError)) {
     case cdm::kNotSupportedError:
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
     case cdm::kInvalidStateError:
       return NS_ERROR_DOM_INVALID_STATE_ERR;
@@ -377,204 +378,96 @@ ToNsresult(uint32_t aError)
       return NS_ERROR_DOM_ABORT_ERR; // Note: Unique placeholder.
     case cdm::kOutputError:
       return NS_ERROR_DOM_SECURITY_ERR; // Note: Unique placeholder.
   };
   MOZ_ASSERT_UNREACHABLE("Invalid cdm::Error enum value.");
   return NS_ERROR_DOM_TIMEOUT_ERR; // Note: Unique placeholder.
 }
 
-void
-ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
-                                 nsresult aError,
-                                 const nsCString& aErrorMessage)
-{
-  GMP_LOG(
-    "ChromiumCDMParent::RejectPromise(this=%p, pid=%u)", this, aPromiseId);
-  // Note: The MediaKeys rejects all pending DOM promises when it
-  // initiates shutdown.
-  if (!mProxy || mIsShutdown) {
-    return;
-  }
-
-  mMainThread->Dispatch(
-    NewRunnableMethod<uint32_t, nsresult, nsCString>(
-      "ChromiumCDMProxy::RejectPromise",
-      mProxy,
-      &ChromiumCDMProxy::RejectPromise,
-      aPromiseId,
-      aError,
-      aErrorMessage),
-    NS_DISPATCH_NORMAL);
-}
-
 ipc::IPCResult
 ChromiumCDMParent::RecvOnRejectPromise(const uint32_t& aPromiseId,
                                        const uint32_t& aError,
                                        const uint32_t& aSystemCode,
                                        const nsCString& aErrorMessage)
 {
   RejectPromise(aPromiseId, ToNsresult(aError), aErrorMessage);
   return IPC_OK();
 }
 
-static dom::MediaKeyMessageType
-ToDOMMessageType(uint32_t aMessageType)
-{
-  switch (static_cast<cdm::MessageType>(aMessageType)) {
-    case cdm::kLicenseRequest:
-      return dom::MediaKeyMessageType::License_request;
-    case cdm::kLicenseRenewal:
-      return dom::MediaKeyMessageType::License_renewal;
-    case cdm::kLicenseRelease:
-      return dom::MediaKeyMessageType::License_release;
-  }
-  MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value.");
-  return dom::MediaKeyMessageType::License_request;
-}
-
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionMessage(const nsCString& aSessionId,
                                         const uint32_t& aMessageType,
                                         nsTArray<uint8_t>&& aMessage)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
           this,
           aSessionId.get());
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return IPC_OK();
   }
-  RefPtr<CDMProxy> proxy = mProxy;
-  nsString sid = NS_ConvertUTF8toUTF16(aSessionId);
-  dom::MediaKeyMessageType messageType = ToDOMMessageType(aMessageType);
-  nsTArray<uint8_t> msg(Move(aMessage));
 
-  mMainThread->Dispatch(
-    NS_NewRunnableFunction("gmp::ChromiumCDMParent::RecvOnSessionMessage",
-                           [proxy, sid, messageType, msg]() mutable {
-                             proxy->OnSessionMessage(sid, messageType, msg);
-                           }),
-    NS_DISPATCH_NORMAL);
+  mCDMCallback->SessionMessage(aSessionId, aMessageType, Move(aMessage));
   return IPC_OK();
 }
 
-static dom::MediaKeyStatus
-ToDOMMediaKeyStatus(uint32_t aStatus)
-{
-  switch (static_cast<cdm::KeyStatus>(aStatus)) {
-    case cdm::kUsable:
-      return dom::MediaKeyStatus::Usable;
-    case cdm::kInternalError:
-      return dom::MediaKeyStatus::Internal_error;
-    case cdm::kExpired:
-      return dom::MediaKeyStatus::Expired;
-    case cdm::kOutputRestricted:
-      return dom::MediaKeyStatus::Output_restricted;
-    case cdm::kOutputDownscaled:
-      return dom::MediaKeyStatus::Output_downscaled;
-    case cdm::kStatusPending:
-      return dom::MediaKeyStatus::Status_pending;
-    case cdm::kReleased:
-      return dom::MediaKeyStatus::Released;
-  }
-  MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value.");
-  return dom::MediaKeyStatus::Internal_error;
-}
-
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionKeysChange(
   const nsCString& aSessionId,
   nsTArray<CDMKeyInformation>&& aKeysInfo)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return IPC_OK();
   }
-  bool keyStatusesChange = false;
-  {
-    CDMCaps::AutoLock caps(mProxy->Capabilites());
-    for (size_t i = 0; i < aKeysInfo.Length(); i++) {
-      keyStatusesChange |=
-        caps.SetKeyStatus(aKeysInfo[i].mKeyId(),
-                          NS_ConvertUTF8toUTF16(aSessionId),
-                          dom::Optional<dom::MediaKeyStatus>(
-                            ToDOMMediaKeyStatus(aKeysInfo[i].mStatus())));
-    }
-  }
-  if (keyStatusesChange) {
-    mMainThread->Dispatch(
-      NewRunnableMethod<nsString>("ChromiumCDMProxy::OnKeyStatusesChange",
-                                  mProxy,
-                                  &ChromiumCDMProxy::OnKeyStatusesChange,
-                                  NS_ConvertUTF8toUTF16(aSessionId)),
-      NS_DISPATCH_NORMAL);
-  }
+
+  mCDMCallback->SessionKeysChange(aSessionId, Move(aKeysInfo));
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnExpirationChange(const nsCString& aSessionId,
                                           const double& aSecondsSinceEpoch)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
           this,
           aSecondsSinceEpoch);
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return IPC_OK();
   }
 
-  mMainThread->Dispatch(
-    NewRunnableMethod<nsString, UnixTime>(
-      "ChromiumCDMProxy::OnExpirationChange",
-      mProxy,
-      &ChromiumCDMProxy::OnExpirationChange,
-      NS_ConvertUTF8toUTF16(aSessionId),
-      GMPTimestamp(aSecondsSinceEpoch * 1000)),
-    NS_DISPATCH_NORMAL);
+  mCDMCallback->ExpirationChange(aSessionId, aSecondsSinceEpoch);
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnSessionClosed(const nsCString& aSessionId)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return IPC_OK();
   }
 
-  mMainThread->Dispatch(
-    NewRunnableMethod<nsString>("ChromiumCDMProxy::OnSessionClosed",
-                                mProxy,
-                                &ChromiumCDMProxy::OnSessionClosed,
-                                NS_ConvertUTF8toUTF16(aSessionId)),
-    NS_DISPATCH_NORMAL);
+  mCDMCallback->SessionClosed(aSessionId);
   return IPC_OK();
 }
 
 ipc::IPCResult
 ChromiumCDMParent::RecvOnLegacySessionError(const nsCString& aSessionId,
                                             const uint32_t& aError,
                                             const uint32_t& aSystemCode,
                                             const nsCString& aMessage)
 {
   GMP_LOG("ChromiumCDMParent::RecvOnLegacySessionError(this=%p)", this);
-  if (!mProxy || mIsShutdown) {
+  if (!mCDMCallback || mIsShutdown) {
     return IPC_OK();
   }
 
-  mMainThread->Dispatch(
-    NewRunnableMethod<nsString, nsresult, uint32_t, nsString>(
-      "ChromiumCDMProxy::OnSessionError",
-      mProxy,
-      &ChromiumCDMProxy::OnSessionError,
-      NS_ConvertUTF8toUTF16(aSessionId),
-      ToNsresult(aError),
-      aSystemCode,
-      NS_ConvertUTF8toUTF16(aMessage)),
-    NS_DISPATCH_NORMAL);
+  mCDMCallback->LegacySessionError(
+    aSessionId, ToNsresult(aError), aSystemCode, aMessage);
   return IPC_OK();
 }
 
 DecryptStatus
 ToDecryptStatus(uint32_t aError)
 {
   switch (static_cast<cdm::Status>(aError)) {
     case cdm::kSuccess:
@@ -929,37 +822,32 @@ ChromiumCDMParent::RecvShutdown()
 }
 
 void
 ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   GMP_LOG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this, aWhy);
   MOZ_ASSERT(!mActorDestroyed);
   mActorDestroyed = true;
-  // Shutdown() will clear mProxy, so let's keep a reference for later use.
-  RefPtr<ChromiumCDMProxy> proxy = mProxy;
+  // Shutdown() will clear mCDMCallback, so let's keep a reference for later use.
+  auto callback = mCDMCallback;
   if (!mIsShutdown) {
     // Plugin crash.
     MOZ_ASSERT(aWhy == AbnormalShutdown);
     Shutdown();
   }
   MOZ_ASSERT(mIsShutdown);
   RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
   if (mContentParent) {
     mContentParent->ChromiumCDMDestroyed(this);
     mContentParent = nullptr;
   }
   bool abnormalShutdown = (aWhy == AbnormalShutdown);
-  if (abnormalShutdown && proxy) {
-    mMainThread->Dispatch(
-      NewRunnableMethod(
-        "ChromiumCDMProxy::Terminated",
-        proxy,
-        &ChromiumCDMProxy::Terminated),
-      NS_DISPATCH_NORMAL);
+  if (abnormalShutdown && callback) {
+    callback->Terminated();
   }
   MaybeDisconnect(abnormalShutdown);
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 ChromiumCDMParent::InitializeVideoDecoder(
   const gmp::CDMVideoDecoderConfig& aConfig,
   const VideoInfo& aInfo,
@@ -1185,29 +1073,24 @@ ChromiumCDMParent::Shutdown()
   }
   mIsShutdown = true;
 
   // If we're shutting down due to the plugin shutting down due to application
   // shutdown, we should tell the CDM proxy to also shutdown. Otherwise the
   // proxy will shutdown when the owning MediaKeys is destroyed during cycle
   // collection, and that will not shut down cleanly as the GMP thread will be
   // shutdown by then.
-  if (mProxy) {
-    mMainThread->Dispatch(
-      NewRunnableMethod(
-        "ChromiumCDMProxy::Shutdown",
-        mProxy,
-        &ChromiumCDMProxy::Shutdown),
-      NS_DISPATCH_NORMAL);
+  if (mCDMCallback) {
+    mCDMCallback->Shutdown();
   }
 
-  // We may be called from a task holding the last reference to the proxy, so
+  // We may be called from a task holding the last reference to the CDM callback, so
   // let's clear our local weak pointer to ensure it will not be used afterward
   // (including from an already-queued task, e.g.: ActorDestroy).
-  mProxy = nullptr;
+  mCDMCallback = nullptr;
 
   mReorderQueue.Clear();
 
   for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
     decrypt->PostResult(eme::AbortedErr);
   }
   mDecrypts.Clear();
 
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -13,16 +13,18 @@
 #include "mozilla/gmp/PChromiumCDMParent.h"
 #include "mozilla/RefPtr.h"
 #include "nsDataHashtable.h"
 #include "PlatformDecoderModule.h"
 #include "ImageContainer.h"
 #include "mozilla/Span.h"
 #include "ReorderQueue.h"
 
+class ChromiumCDMCallback;
+
 namespace mozilla {
 
 class MediaRawData;
 class ChromiumCDMProxy;
 
 namespace gmp {
 
 class GMPContentParent;
@@ -33,17 +35,17 @@ class ChromiumCDMParent final
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMParent)
 
   ChromiumCDMParent(GMPContentParent* aContentParent, uint32_t aPluginId);
 
   uint32_t PluginId() const { return mPluginId; }
 
-  bool Init(ChromiumCDMProxy* aProxy,
+  bool Init(ChromiumCDMCallback* aCDMCallback,
             bool aAllowDistinctiveIdentifier,
             bool aAllowPersistentState,
             nsIEventTarget* aMainThread);
 
   void CreateSession(uint32_t aCreateSessionToken,
                      uint32_t aSessionType,
                      uint32_t aInitDataType,
                      uint32_t aPromiseId,
@@ -143,20 +145,19 @@ protected:
 
   bool PurgeShmems();
   bool EnsureSufficientShmems(size_t aVideoFrameSize);
   already_AddRefed<VideoData> CreateVideoFrame(const CDMVideoFrame& aFrame,
                                                Span<uint8_t> aData);
 
   const uint32_t mPluginId;
   GMPContentParent* mContentParent;
-  // Note: this pointer is a weak reference because otherwise it would cause
-  // a cycle, as ChromiumCDMProxy has a strong reference to the
-  // ChromiumCDMParent.
-  ChromiumCDMProxy* mProxy = nullptr;
+  // Note: this pointer is a weak reference as ChromiumCDMProxy has a strong reference to the
+  // ChromiumCDMCallback.
+  ChromiumCDMCallback* mCDMCallback = nullptr;
   nsDataHashtable<nsUint32HashKey, uint32_t> mPromiseToCreateSessionToken;
   nsTArray<RefPtr<DecryptJob>> mDecrypts;
 
   MozPromiseHolder<MediaDataDecoder::InitPromise> mInitVideoDecoderPromise;
   MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
 
   RefPtr<layers::ImageContainer> mImageContainer;
   VideoInfo mVideoInfo;
@@ -183,15 +184,15 @@ protected:
   // before presenting. mMaxRefFrames is non-zero if we have an initialized
   // decoder and we are decoding H.264. If so, it stores the maximum length of
   // the reorder queue that we need. Note we may have multiple decoders for the
   // life time of this object, but never more than one active at once.
   uint32_t mMaxRefFrames = 0;
   ReorderQueue mReorderQueue;
 
   // The main thread associated with the root document. Must be set in Init().
-    nsCOMPtr<nsIEventTarget> mMainThread;
+  nsCOMPtr<nsIEventTarget> mMainThread;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // ChromiumCDMParent_h_
--- a/dom/media/gmp/ChromiumCDMProxy.cpp
+++ b/dom/media/gmp/ChromiumCDMProxy.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ChromiumCDMProxy.h"
+#include "ChromiumCDMCallbackProxy.h"
 #include "mozilla/dom/MediaKeySession.h"
 #include "GMPUtils.h"
 #include "nsPrintfCString.h"
 #include "GMPService.h"
 #include "content_decryption_module.h"
 
 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
 
@@ -92,17 +93,19 @@ ChromiumCDMProxy::Init(PromiseId aPromis
         return;
       }
       RefPtr<gmp::GetCDMParentPromise> promise =
         service->GetCDM(nodeId, { keySystem }, helper);
       promise->Then(
         thread,
         __func__,
         [self, aPromiseId](RefPtr<gmp::ChromiumCDMParent> cdm) {
-          if (!cdm->Init(self,
+          self->mCallback =
+            MakeUnique<ChromiumCDMCallbackProxy>(self, self->mMainThread);
+          if (!cdm->Init(self->mCallback.get(),
                          self->mDistinctiveIdentifierRequired,
                          self->mPersistentStateRequired,
                          self->mMainThread)) {
             self->RejectPromise(aPromiseId,
                                 NS_ERROR_FAILURE,
                                 NS_LITERAL_CSTRING("GetCDM failed."));
             return;
           }
@@ -459,17 +462,17 @@ ChromiumCDMProxy::OnResolveLoadSessionPr
     return;
   }
   mKeys->OnSessionLoaded(aPromiseId, aSuccess);
 }
 
 void
 ChromiumCDMProxy::OnSessionMessage(const nsAString& aSessionId,
                                    dom::MediaKeyMessageType aMessageType,
-                                   nsTArray<uint8_t>& aMessage)
+                                   const nsTArray<uint8_t>& aMessage)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
   }
   RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
   if (session) {
     session->DispatchKeyMessage(aMessageType, aMessage);
--- a/dom/media/gmp/ChromiumCDMProxy.h
+++ b/dom/media/gmp/ChromiumCDMProxy.h
@@ -10,17 +10,17 @@
 #include "mozilla/CDMProxy.h"
 #include "mozilla/AbstractThread.h"
 #include "ChromiumCDMParent.h"
 
 namespace mozilla {
 
 class MediaRawData;
 class DecryptJob;
-
+class ChromiumCDMCallbackProxy;
 class ChromiumCDMProxy : public CDMProxy
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMProxy, override)
 
   ChromiumCDMProxy(dom::MediaKeys* aKeys,
                    const nsAString& aKeySystem,
                    GMPCrashHelper* aCrashHelper,
@@ -63,17 +63,17 @@ public:
 
   void OnSetSessionId(uint32_t aCreateSessionToken,
                       const nsAString& aSessionId) override;
 
   void OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) override;
 
   void OnSessionMessage(const nsAString& aSessionId,
                         dom::MediaKeyMessageType aMessageType,
-                        nsTArray<uint8_t>& aMessage) override;
+                        const nsTArray<uint8_t>& aMessage) override;
 
   void OnExpirationChange(const nsAString& aSessionId,
                           GMPTimestamp aExpiryTime) override;
 
   void OnSessionClosed(const nsAString& aSessionId) override;
 
   void OnSessionError(const nsAString& aSessionId,
                       nsresult aException,
@@ -120,13 +120,14 @@ private:
 
   ~ChromiumCDMProxy();
 
   GMPCrashHelper* mCrashHelper;
 
   Mutex mCDMMutex;
   RefPtr<gmp::ChromiumCDMParent> mCDM;
   RefPtr<AbstractThread> mGMPThread;
+  UniquePtr<ChromiumCDMCallbackProxy> mCallback;
 };
 
 } // namespace mozilla
 
 #endif // GMPCDMProxy_h_
--- a/dom/media/gmp/moz.build
+++ b/dom/media/gmp/moz.build
@@ -7,16 +7,17 @@
 XPIDL_MODULE = 'content_geckomediaplugins'
 
 XPIDL_SOURCES += [
     'mozIGeckoMediaPluginChromeService.idl',
     'mozIGeckoMediaPluginService.idl',
 ]
 
 EXPORTS += [
+    'ChromiumCDMCallback.h',
     'ChromiumCDMParent.h',
     'ChromiumCDMProxy.h',
     'DecryptJob.h',
     'gmp-api/gmp-decryption.h',
     'gmp-api/gmp-entrypoints.h',
     'gmp-api/gmp-errors.h',
     'gmp-api/gmp-platform.h',
     'gmp-api/gmp-storage.h',
@@ -67,16 +68,17 @@ EXPORTS += [
     'GMPVideoPlaneImpl.h',
     'widevine-adapter/content_decryption_module.h',
     'widevine-adapter/content_decryption_module_export.h',
     'widevine-adapter/content_decryption_module_ext.h',
 ]
 
 UNIFIED_SOURCES += [
     'ChromiumCDMAdapter.cpp',
+    'ChromiumCDMCallbackProxy.cpp',
     'ChromiumCDMChild.cpp',
     'ChromiumCDMParent.cpp',
     'ChromiumCDMProxy.cpp',
     'DecryptJob.cpp',
     'GMPCDMCallbackProxy.cpp',
     'GMPChild.cpp',
     'GMPContentChild.cpp',
     'GMPContentParent.cpp',
--- a/dom/plugins/ipc/hangui/moz.build
+++ b/dom/plugins/ipc/hangui/moz.build
@@ -10,17 +10,17 @@ UNIFIED_SOURCES += [
     'MiniShmChild.cpp',
     'PluginHangUIChild.cpp',
 ]
 include('/ipc/chromium/chromium-config.mozbuild')
 
 DEFINES['NS_NO_XPCOM'] = True
 DEFINES['_HAS_EXCEPTIONS'] = 0
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 if CONFIG['GNU_CC']:
     WIN32_EXE_LDFLAGS += ['-municode']
 
 RCINCLUDE = 'HangUIDlg.rc'
 
 OS_LIBS += [
     'comctl32',
--- a/dom/plugins/test/testplugin/testplugin.mozbuild
+++ b/dom/plugins/test/testplugin/testplugin.mozbuild
@@ -30,17 +30,17 @@ elif toolkit == 'windows':
         'msimg32',
         'imm32'
     ]
 
 # must link statically with the CRT; nptest isn't Gecko code
 USE_STATIC_LIBS = True
 
 # Don't use STL wrappers; nptest isn't Gecko code
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 NO_PGO = True
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     RCFILE  = 'nptest.rc'
     RESFILE = 'nptest.res'
     DEFFILE = SRCDIR + '/nptest.def'
 
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -2,17 +2,16 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "hasht.h"
 #include "nsICryptoHash.h"
 #include "nsNetCID.h"
-#include "nsNetUtil.h" // Used by WD-05 compat support (Remove in Bug 1381126)
 #include "nsThreadUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/AuthenticatorAttestationResponse.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WebAuthnCBORUtil.h"
 #include "mozilla/dom/WebAuthnManager.h"
 #include "mozilla/dom/WebAuthnUtil.h"
 #include "mozilla/dom/PWebAuthnTransaction.h"
@@ -186,35 +185,17 @@ RelaxSameOrigin(nsPIDOMWindowInner* aPar
   if (!document || !document->IsHTMLDocument()) {
     return NS_ERROR_FAILURE;
   }
   nsHTMLDocument* html = document->AsHTMLDocument();
   if (NS_WARN_IF(!html)) {
     return NS_ERROR_FAILURE;
   }
 
-  // WD-05 origin compatibility support - aInputRpId might be a URI/origin,
-  // so catch that (Bug 1380421). Remove in Bug 1381126.
-  nsAutoString inputRpId(aInputRpId);
-  nsCOMPtr<nsIURI> inputUri;
-  if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(inputUri), aInputRpId))) {
-    // If we parsed the input as a URI, then pull out the host and use it as the
-    // input
-    nsAutoCString uriHost;
-    if (NS_FAILED(inputUri->GetHost(uriHost))) {
-      return NS_ERROR_FAILURE;
-    }
-    CopyUTF8toUTF16(uriHost, inputRpId);
-    MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug,
-            ("WD-05 Fallback: Parsed input %s URI into host %s",
-             NS_ConvertUTF16toUTF8(aInputRpId).get(), uriHost.get()));
-  }
-  // End WD-05 origin compatibility support (Bug 1380421)
-
-  if (!html->IsRegistrableDomainSuffixOfOrEqualTo(inputRpId, originHost)) {
+  if (!html->IsRegistrableDomainSuffixOfOrEqualTo(aInputRpId, originHost)) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId));
   return NS_OK;
 }
 
 static void
@@ -486,20 +467,18 @@ WebAuthnManager::MakeCredential(nsPIDOMW
   // and compute the clientDataJSON and clientDataHash.
 
   CryptoBuffer challenge;
   if (!challenge.Assign(aOptions.mChallenge)) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
-  // WD-05 vs. WD-06: In WD-06, the first parameter should be "origin". Fix
-  // this in Bug 1384776
   nsAutoCString clientDataJSON;
-  srv = AssembleClientData(NS_ConvertUTF8toUTF16(rpId), challenge, clientDataJSON);
+  srv = AssembleClientData(origin, challenge, clientDataJSON);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   CryptoBuffer clientDataHash;
   if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
@@ -650,19 +629,17 @@ WebAuthnManager::GetAssertion(nsPIDOMWin
   // the clientDataJSON and clientDataHash.
   CryptoBuffer challenge;
   if (!challenge.Assign(aOptions.mChallenge)) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   nsAutoCString clientDataJSON;
-  // WD-05 vs. WD-06: In WD-06, the first parameter should be "origin". Fix
-  // this in Bug 1384776
-  srv = AssembleClientData(NS_ConvertUTF8toUTF16(rpId), challenge, clientDataJSON);
+  srv = AssembleClientData(origin, challenge, clientDataJSON);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   CryptoBuffer clientDataHash;
   if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -58,24 +58,32 @@ function() {
 
     ok(aCredInfo.rawId === aCredInfo.rawId, "PublicKeyCredential.RawID is SameObject");
     ok(aCredInfo.response === aCredInfo.response, "PublicKeyCredential.Response is SameObject");
     ok(aCredInfo.response.clientDataJSON === aCredInfo.response.clientDataJSON, "PublicKeyCredential.Response.ClientDataJSON is SameObject");
     ok(aCredInfo.response.attestationObject === aCredInfo.response.attestationObject, "PublicKeyCredential.Response.AttestationObject is SameObject");
 
     let clientData = JSON.parse(buffer2string(aCredInfo.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct");
-    // WD-05 vs. WD-06: In WD-06, the second parameter should be "window.location.origin". Fix
-    // this in Bug 1384776
-    is(clientData.origin, document.domain, "Origin is correct");
+    is(clientData.origin, window.location.origin, "Origin is correct");
     is(clientData.hashAlg, "SHA-256", "Hash algorithm is correct");
 
     return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject.buffer)
     .then(function(decodedResult) {
-      ok(decodedResult.flags == (flag_TUP | flag_AT), "User presence and Attestation Object must both be set");
+      // Make sure the RP ID hash matches what we calculate.
+      return crypto.subtle.digest("SHA-256", string2buffer(document.domain))
+      .then(function(calculatedHash) {
+        is(bytesToBase64(new Uint8Array(calculatedHash)), bytesToBase64(decodedResult.rpIdHash),
+           "Calculated RP ID hash must match what the browser derived.");
+        return Promise.resolve(decodedResult);
+      });
+    })
+    .then(function(decodedResult) {
+      ok(decodedResult.flags == (flag_TUP | flag_AT),
+         "User presence and Attestation Object must both be set");
 
       aCredInfo.clientDataObj = clientData;
       aCredInfo.publicKeyHandle = decodedResult.publicKeyHandle;
       aCredInfo.attestationObject = decodedResult.attestationObject;
       return aCredInfo;
     });
   }
 
@@ -96,19 +104,17 @@ function() {
     is(aAssertion.id, bytesToBase64UrlSafe(aAssertion.rawId), "Encoded Key ID and Raw Key ID match");
 
     ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
     ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
 
     ok(aAssertion.response.authenticatorData.length > 0, "Authenticator data exists");
     let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
-    // WD-05 vs. WD-06: In WD-06, the second parameter should be "window.location.origin". Fix
-    // this in Bug 1384776
-    is(clientData.origin, document.domain, "Origin is correct");
+    is(clientData.origin, window.location.origin, "Origin is correct");
     is(clientData.hashAlg, "SHA-256", "Hash algorithm is correct");
 
     return webAuthnDecodeAttestation(aAssertion.response.authenticatorData)
     .then(function(decodedResult) {
       ok(decodedResult.flags == flag_TUP, "User presence must be the only flag set");
       is(decodedResult.counter.length, 4, "Counter must be 4 bytes");
       return deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, decodedResult)
     })
--- a/dom/webauthn/tests/test_webauthn_sameorigin.html
+++ b/dom/webauthn/tests/test_webauthn_sameorigin.html
@@ -226,40 +226,37 @@
             challenge: chall,
             rpId: "alt.test",
             allowList: [gTrackedCredential["basic"]]
           };
           return credm.get({publicKey: publicKeyCredentialRequestOptions})
                       .then(arrivingHereIsBad)
                       .catch(expectSecurityError);
         },
-        // These next two tests should be removed in Bug 1381126.
         function () {
           // Test basic good Create call but using an origin (Bug 1380421)
           let rp = {id: window.origin};
           let makeCredentialOptions = {
             rp: rp, user: user, challenge: chall, parameters: [param]
           };
           return credm.create({publicKey: makeCredentialOptions})
-                      .then(keepThisPublicKeyCredential("origin"))
-                      .then(arrivingHereIsGood)
-                      .catch(arrivingHereIsBad);
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
         },
         function () {
           // Test basic good Get call but using an origin (Bug 1380421)
           let publicKeyCredentialRequestOptions = {
             challenge: chall,
             rpId: window.origin,
-            allowList: [gTrackedCredential["origin"]]
+            allowList: [gTrackedCredential["basic"]]
           };
           return credm.get({publicKey: publicKeyCredentialRequestOptions})
-                      .then(arrivingHereIsGood)
-                      .catch(arrivingHereIsBad);
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
         }
-        // End remove in Bug 1381126.
       ];
       var i = 0;
       var runNextTest = () => {
         if (i == testFuncs.length) {
           SimpleTest.finish();
           return;
         }
         console.log(i, testFuncs[i], testFuncs.length);
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -407,17 +407,21 @@ fetch('interrupt.sjs')
 }, function(e) {
   my_ok(false, "interrupted fetch failed");
   finish();
 });
 
 ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'].forEach(function(method) {
   fetchXHRWithMethod('xhr-method-test.txt', method, function(xhr) {
     my_ok(xhr.status == 200, method + " load should be successful");
-    my_ok(xhr.responseText == ("intercepted " + method), method + " load should have synthesized response");
+    if (method === "HEAD") {
+      my_ok(xhr.responseText == "", method + "load should not have synthesized response");
+    } else {
+      my_ok(xhr.responseText == ("intercepted " + method), method + " load should have synthesized response");
+    }
     finish();
   });
 });
 
 expectAsyncResult();
 fetch(new Request('empty-header', {headers:{"emptyheader":""}}))
 .then(function(res) {
   return res.text();
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -619,16 +619,22 @@ XMLHttpRequestMainThread::GetResponseTex
     aSnapshot.SetVoid();
     return;
   }
 
   if (mState != State::loading && mState != State::done) {
     return;
   }
 
+  // Main Fetch step 18 requires to ignore body for head/connect methods.
+  if (mRequestMethod.EqualsLiteral("HEAD") ||
+      mRequestMethod.EqualsLiteral("CONNECT")) {
+    return;
+  }
+
   // We only decode text lazily if we're also parsing to a doc.
   // Also, if we've decoded all current data already, then no need to decode
   // more.
   if ((!mResponseXML && !mErrorParsingXML) ||
       mResponseBodyDecodedPos == mResponseBody.Length()) {
     mResponseText.CreateSnapshot(aSnapshot);
     return;
   }
@@ -2076,25 +2082,21 @@ XMLHttpRequestMainThread::OnStartRequest
           contentLength > 0 &&
           contentLength < XML_HTTP_REQUEST_MAX_CONTENT_LENGTH_PREALLOCATE) {
         mArrayBufferBuilder.setCapacity(static_cast<int32_t>(contentLength));
       }
     }
   }
 
   // Set up responseXML
-  bool parseBody = mResponseType == XMLHttpRequestResponseType::_empty ||
-                   mResponseType == XMLHttpRequestResponseType::Document;
-  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
-  if (parseBody && httpChannel) {
-    nsAutoCString method;
-    rv = httpChannel->GetRequestMethod(method);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-    parseBody = !method.EqualsLiteral("HEAD");
-  }
+  // Note: Main Fetch step 18 requires to ignore body for head/connect methods.
+  bool parseBody = (mResponseType == XMLHttpRequestResponseType::_empty ||
+                    mResponseType == XMLHttpRequestResponseType::Document) &&
+                   !(mRequestMethod.EqualsLiteral("HEAD") ||
+                     mRequestMethod.EqualsLiteral("CONNECT"));
 
   mIsHtml = false;
   mWarnAboutSyncHtml = false;
   if (parseBody && NS_SUCCEEDED(status)) {
     // We can gain a huge performance win by not even trying to
     // parse non-XML data. This also protects us from the situation
     // where we have an XML document and sink, but HTML (or other)
     // parser, which can produce unreliable results.
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -200,26 +200,18 @@ APZEventState::ProcessSingleTap(const CS
     return;
   }
 
   if (mTouchEndCancelled) {
     return;
   }
 
   LayoutDevicePoint ldPoint = aPoint * aScale;
-  if (!mActiveElementManager->ActiveElementUsesStyle()) {
-    // If the active element isn't visually affected by the :active style, we
-    // have no need to wait the extra sActiveDurationMs to make the activation
-    // visually obvious to the user.
-    widget::nsAutoRollup rollup(touchRollup.get());
-    APZCCallbackHelper::FireSingleTapEvent(ldPoint, aModifiers, aClickCount, widget);
-    return;
-  }
 
-  APZES_LOG("Active element uses style, scheduling timer for click event\n");
+  APZES_LOG("Scheduling timer for click event\n");
   nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
   dom::TabChild* tabChild = widget->GetOwningTabChild();
 
   if (tabChild && XRE_IsContentProcess()) {
     timer->SetTarget(
       tabChild->TabGroup()->EventTargetFor(TaskCategory::Other));
   }
   RefPtr<DelayedFireSingleTapEvent> callback =
--- a/gfx/layers/apz/util/ActiveElementManager.cpp
+++ b/gfx/layers/apz/util/ActiveElementManager.cpp
@@ -22,18 +22,17 @@ namespace mozilla {
 namespace layers {
 
 static int32_t sActivationDelayMs = 100;
 static bool sActivationDelayMsSet = false;
 
 ActiveElementManager::ActiveElementManager()
   : mCanBePan(false),
     mCanBePanSet(false),
-    mSetActiveTask(nullptr),
-    mActiveElementUsesStyle(false)
+    mSetActiveTask(nullptr)
 {
   if (!sActivationDelayMsSet) {
     Preferences::AddIntVarCache(&sActivationDelayMs,
                                 "ui.touch_activation.delay_ms",
                                 sActivationDelayMs);
     sActivationDelayMsSet = true;
   }
 }
@@ -137,67 +136,36 @@ ActiveElementManager::HandleTouchEndEven
 
 void
 ActiveElementManager::HandleTouchEnd()
 {
   AEM_LOG("Touch end, clearing pan state\n");
   mCanBePanSet = false;
 }
 
-bool
-ActiveElementManager::ActiveElementUsesStyle() const
-{
-  return mActiveElementUsesStyle;
-}
-
 static nsPresContext*
 GetPresContextFor(nsIContent* aContent)
 {
   if (!aContent) {
     return nullptr;
   }
   nsIPresShell* shell = aContent->OwnerDoc()->GetShell();
   if (!shell) {
     return nullptr;
   }
   return shell->GetPresContext();
 }
 
-static bool
-ElementHasActiveStyle(dom::Element* aElement)
-{
-  nsPresContext* pc = GetPresContextFor(aElement);
-  if (!pc) {
-    return false;
-  }
-  nsStyleSet* styleSet = pc->StyleSet()->GetAsGecko();
-  if (!styleSet) {
-    // Bug 1397434 tracks making this optimization work for stylo.
-    AEM_LOG("Element %p uses Servo style backend, assuming dependence on active state\n", aElement);
-    return true;
-  }
-
-  for (dom::Element* e = aElement; e; e = e->GetParentElement()) {
-    if (styleSet->HasStateDependentStyle(e, NS_EVENT_STATE_ACTIVE)) {
-      AEM_LOG("Element %p's style is dependent on the active state\n", e);
-      return true;
-    }
-  }
-  AEM_LOG("Element %p doesn't use active styles\n", aElement);
-  return false;
-}
-
 void
 ActiveElementManager::SetActive(dom::Element* aTarget)
 {
   AEM_LOG("Setting active %p\n", aTarget);
 
   if (nsPresContext* pc = GetPresContextFor(aTarget)) {
     pc->EventStateManager()->SetContentState(aTarget, NS_EVENT_STATE_ACTIVE);
-    mActiveElementUsesStyle = ElementHasActiveStyle(aTarget);
   }
 }
 
 void
 ActiveElementManager::ResetActive()
 {
   AEM_LOG("Resetting active from %p\n", mTarget.get());
 
--- a/gfx/layers/apz/util/ActiveElementManager.h
+++ b/gfx/layers/apz/util/ActiveElementManager.h
@@ -54,22 +54,16 @@ public:
    * @param aWasClick whether the touch was a click
    */
   void HandleTouchEndEvent(bool aWasClick);
   /**
    * Handle a touch-end state notification from APZ. This notification may be
    * delayed until after touch listeners have responded to the APZ.
    */
   void HandleTouchEnd();
-  /**
-   * @return true iff the currently active element (or one of its ancestors)
-   * actually had a style for the :active pseudo-class. The currently active
-   * element is the root element if no other elements are active.
-   */
-  bool ActiveElementUsesStyle() const;
 private:
   /**
    * The target of the first touch point in the current touch block.
    */
   nsCOMPtr<dom::Element> mTarget;
   /**
    * Whether the current touch block can be a pan. Set in HandleTouchStart().
    */
@@ -79,20 +73,16 @@ private:
    * We need to keep track of this to allow HandleTouchStart() and
    * SetTargetElement() to be called in either order.
    */
   bool mCanBePanSet;
   /**
    * A task for calling SetActive() after a timeout.
    */
   RefPtr<CancelableRunnable> mSetActiveTask;
-  /**
-   * See ActiveElementUsesStyle() documentation.
-   */
-  bool mActiveElementUsesStyle;
 
   // Helpers
   void TriggerElementActivation();
   void SetActive(dom::Element* aTarget);
   void ResetActive();
   void ResetTouchBlockState();
   void SetActiveTask(const nsCOMPtr<dom::Element>& aTarget);
   void CancelTask();
--- a/ipc/chromium/src/base/histogram.cc
+++ b/ipc/chromium/src/base/histogram.cc
@@ -225,19 +225,16 @@ bool Histogram::HasConstructorTimeDeltaA
 bool Histogram::HasValidRangeChecksum() const {
   return CalculateRangeChecksum() == range_checksum_;
 }
 
 size_t Histogram::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   size_t n = 0;
   n += aMallocSizeOf(this);
-  // We're not allowed to do deep dives into STL data structures.  This
-  // is as close as we can get to measuring this array.
-  n += aMallocSizeOf(&ranges_[0]);
   n += sample_.SizeOfExcludingThis(aMallocSizeOf);
   return n;
 }
 
 size_t
 Histogram::SampleSet::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
 {
   // We're not allowed to do deep dives into STL data structures.  This
@@ -246,40 +243,37 @@ Histogram::SampleSet::SizeOfExcludingThi
 }
 
 Histogram::Histogram(Sample minimum, Sample maximum, size_t bucket_count)
   : sample_(),
     declared_min_(minimum),
     declared_max_(maximum),
     bucket_count_(bucket_count),
     flags_(kNoFlags),
-    ranges_(bucket_count + 1, 0),
     range_checksum_(0) {
   Initialize();
 }
 
 Histogram::Histogram(TimeDelta minimum, TimeDelta maximum, size_t bucket_count)
   : sample_(),
     declared_min_(static_cast<int> (minimum.InMilliseconds())),
     declared_max_(static_cast<int> (maximum.InMilliseconds())),
     bucket_count_(bucket_count),
     flags_(kNoFlags),
-    ranges_(bucket_count + 1, 0),
     range_checksum_(0) {
   Initialize();
 }
 
 Histogram::~Histogram() {
   // Just to make sure most derived class did this properly...
   DCHECK(ValidateBucketRanges());
 }
 
 void Histogram::InitializeBucketRangeFromData(const int* buckets) {
-  DCHECK_EQ(bucket_count_ + 1, ranges_.size());
-  std::copy_n(buckets, bucket_count_, ranges_.data());
+  ranges_ = buckets;
   ResetRangeChecksum();
   DCHECK(ValidateBucketRanges());
 }
 
 bool Histogram::PrintEmptyBucket(size_t index) const {
   return true;
 }
 
@@ -335,52 +329,45 @@ const std::string Histogram::GetAsciiBuc
   return result;
 }
 
 // Update histogram data with new sample.
 void Histogram::Accumulate(Sample value, Count count, size_t index) {
   sample_.Accumulate(value, count, index);
 }
 
-void Histogram::SetBucketRange(size_t i, Sample value) {
-  DCHECK_GT(bucket_count_, i);
-  ranges_[i] = value;
-}
-
 bool Histogram::ValidateBucketRanges() const {
   // Standard assertions that all bucket ranges should satisfy.
-  DCHECK_EQ(bucket_count_ + 1, ranges_.size());
+  DCHECK_EQ(0, ranges_[bucket_count_ + 1]);
   DCHECK_EQ(0, ranges_[0]);
   DCHECK_EQ(declared_min(), ranges_[1]);
   DCHECK_EQ(declared_max(), ranges_[bucket_count_ - 1]);
   DCHECK_EQ(kSampleType_MAX, ranges_[bucket_count_]);
   return true;
 }
 
 uint32_t Histogram::CalculateRangeChecksum() const {
-  DCHECK_EQ(ranges_.size(), bucket_count() + 1);
-  uint32_t checksum = static_cast<uint32_t>(ranges_.size());  // Seed checksum.
+  DCHECK_EQ(0, ranges_[bucket_count_ + 1]);
+  uint32_t checksum = static_cast<uint32_t>(bucket_count_ + 1);  // Seed checksum.
   for (size_t index = 0; index < bucket_count(); ++index)
     checksum = Crc32(checksum, ranges(index));
   return checksum;
 }
 
 void Histogram::Initialize() {
   sample_.Resize(*this);
   if (declared_min_ < 1)
     declared_min_ = 1;
   if (declared_max_ > kSampleType_MAX - 1)
     declared_max_ = kSampleType_MAX - 1;
   DCHECK_LE(declared_min_, declared_max_);
   DCHECK_GT(bucket_count_, 1u);
   CHECK_LT(bucket_count_, kBucketCount_MAX);
   size_t maximal_bucket_count = declared_max_ - declared_min_ + 2;
   DCHECK_LE(bucket_count_, maximal_bucket_count);
-  DCHECK_EQ(0, ranges_[0]);
-  ranges_[bucket_count_] = kSampleType_MAX;
 }
 
 // We generate the CRC-32 using the low order bits to select whether to XOR in
 // the reversed polynomial 0xedb88320L.  This is nice and simple, and allows us
 // to keep the quotient in a uint32_t.  Since we're not concerned about the nature
 // of corruptions (i.e., we don't care about bit sequencing, since we are
 // handling memory changes, which are more grotesque) so we don't bother to
 // get the CRC correct for big-endian vs little-ending calculations.  All we
@@ -480,43 +467,36 @@ void Histogram::SampleSet::Add(const Sam
 //------------------------------------------------------------------------------
 
 LinearHistogram::~LinearHistogram() {
 }
 
 Histogram* LinearHistogram::FactoryGet(Sample minimum,
                                        Sample maximum,
                                        size_t bucket_count,
-                                       Flags flags) {
+                                       Flags flags,
+                                       const int* buckets) {
   Histogram* histogram(NULL);
 
   if (minimum < 1)
     minimum = 1;
   if (maximum > kSampleType_MAX - 1)
     maximum = kSampleType_MAX - 1;
 
   LinearHistogram* linear_histogram =
         new LinearHistogram(minimum, maximum, bucket_count);
-  linear_histogram->InitializeBucketRange();
+  linear_histogram->InitializeBucketRangeFromData(buckets);
   linear_histogram->SetFlags(flags);
   histogram = linear_histogram;
 
   DCHECK_EQ(LINEAR_HISTOGRAM, histogram->histogram_type());
   DCHECK(histogram->HasConstructorArguments(minimum, maximum, bucket_count));
   return histogram;
 }
 
-Histogram* LinearHistogram::FactoryTimeGet(TimeDelta minimum,
-                                           TimeDelta maximum,
-                                           size_t bucket_count,
-                                           Flags flags) {
-  return FactoryGet(minimum.InMilliseconds(), maximum.InMilliseconds(),
-                    bucket_count, flags);
-}
-
 Histogram::ClassType LinearHistogram::histogram_type() const {
   return LINEAR_HISTOGRAM;
 }
 
 void LinearHistogram::Accumulate(Sample value, Count count, size_t index) {
   sample_.Accumulate(value, count, index);
 }
 
@@ -536,29 +516,16 @@ LinearHistogram::LinearHistogram(Sample 
 LinearHistogram::LinearHistogram(TimeDelta minimum,
                                  TimeDelta maximum,
                                  size_t bucket_count)
     : Histogram(minimum >= TimeDelta::FromMilliseconds(1) ?
                                  minimum : TimeDelta::FromMilliseconds(1),
                 maximum, bucket_count) {
 }
 
-void LinearHistogram::InitializeBucketRange() {
-  DCHECK_GT(declared_min(), 0);  // 0 is the underflow bucket here.
-  double min = declared_min();
-  double max = declared_max();
-  size_t i;
-  for (i = 1; i < bucket_count(); ++i) {
-    double linear_range = (min * (bucket_count() -1 - i) + max * (i - 1)) /
-                          (bucket_count() - 2);
-    SetBucketRange(i, static_cast<int> (linear_range + 0.5));
-  }
-  ResetRangeChecksum();
-}
-
 double LinearHistogram::GetBucketSize(Count current, size_t i) const {
   DCHECK_GT(ranges(i + 1), ranges(i));
   // Adjacent buckets with different widths would have "surprisingly" many (few)
   // samples in a histogram if we didn't normalize this way.
   double denominator = ranges(i + 1) - ranges(i);
   return current/denominator;
 }
 
@@ -574,21 +541,21 @@ bool LinearHistogram::PrintEmptyBucket(s
   return bucket_description_.find(ranges(index)) == bucket_description_.end();
 }
 
 
 //------------------------------------------------------------------------------
 // This section provides implementation for BooleanHistogram.
 //------------------------------------------------------------------------------
 
-Histogram* BooleanHistogram::FactoryGet(Flags flags) {
+Histogram* BooleanHistogram::FactoryGet(Flags flags, const int* buckets) {
   Histogram* histogram(NULL);
 
   BooleanHistogram* tentative_histogram = new BooleanHistogram();
-  tentative_histogram->InitializeBucketRange();
+  tentative_histogram->InitializeBucketRangeFromData(buckets);
   tentative_histogram->SetFlags(flags);
   histogram = tentative_histogram;
 
   DCHECK_EQ(BOOLEAN_HISTOGRAM, histogram->histogram_type());
   return histogram;
 }
 
 Histogram::ClassType BooleanHistogram::histogram_type() const {
@@ -611,22 +578,22 @@ BooleanHistogram::Accumulate(Sample valu
   LinearHistogram::Accumulate(!!value, count, value ? 1 : 0);
 }
 
 //------------------------------------------------------------------------------
 // FlagHistogram:
 //------------------------------------------------------------------------------
 
 Histogram *
-FlagHistogram::FactoryGet(Flags flags)
+FlagHistogram::FactoryGet(Flags flags, const int* buckets)
 {
   Histogram *h(nullptr);
 
   FlagHistogram *fh = new FlagHistogram();
-  fh->InitializeBucketRange();
+  fh->InitializeBucketRangeFromData(buckets);
   fh->SetFlags(flags);
   size_t zero_index = fh->BucketIndex(0);
   fh->LinearHistogram::Accumulate(0, 1, zero_index);
   h = fh;
 
   return h;
 }
 
@@ -689,22 +656,22 @@ FlagHistogram::Clear() {
   LinearHistogram::Accumulate(0, 1, zero_index);
 }
 
 //------------------------------------------------------------------------------
 // CountHistogram:
 //------------------------------------------------------------------------------
 
 Histogram *
-CountHistogram::FactoryGet(Flags flags)
+CountHistogram::FactoryGet(Flags flags, const int* buckets)
 {
   Histogram *h(nullptr);
 
   CountHistogram *fh = new CountHistogram();
-  fh->InitializeBucketRange();
+  fh->InitializeBucketRangeFromData(buckets);
   fh->SetFlags(flags);
   h = fh;
 
   return h;
 }
 
 CountHistogram::CountHistogram()
   : LinearHistogram(1, 2, 3) {
@@ -735,67 +702,9 @@ CountHistogram::AddSampleSet(const Sampl
     return;
   }
 
   if (sample.counts(indices[0]) != 0) {
     Accumulate(1, sample.counts(indices[0]), indices[0]);
   }
 }
 
-
-//------------------------------------------------------------------------------
-// CustomHistogram:
-//------------------------------------------------------------------------------
-
-Histogram* CustomHistogram::FactoryGet(const std::vector<Sample>& custom_ranges,
-                                       Flags flags) {
-  Histogram* histogram(NULL);
-
-  // Remove the duplicates in the custom ranges array.
-  std::vector<int> ranges = custom_ranges;
-  ranges.push_back(0);  // Ensure we have a zero value.
-  std::sort(ranges.begin(), ranges.end());
-  ranges.erase(std::unique(ranges.begin(), ranges.end()), ranges.end());
-  if (ranges.size() <= 1) {
-    DCHECK(false);
-    // Note that we pushed a 0 in above, so for defensive code....
-    ranges.push_back(1);  // Put in some data so we can index to [1].
-  }
-
-  DCHECK_LT(ranges.back(), kSampleType_MAX);
-
-  CustomHistogram* custom_histogram = new CustomHistogram(ranges);
-  custom_histogram->InitializedCustomBucketRange(ranges);
-  custom_histogram->SetFlags(flags);
-  histogram = custom_histogram;
-
-  DCHECK_EQ(histogram->histogram_type(), CUSTOM_HISTOGRAM);
-  DCHECK(histogram->HasConstructorArguments(ranges[1], ranges.back(),
-                                            ranges.size()));
-  return histogram;
-}
-
-Histogram::ClassType CustomHistogram::histogram_type() const {
-  return CUSTOM_HISTOGRAM;
-}
-
-CustomHistogram::CustomHistogram(const std::vector<Sample>& custom_ranges)
-    : Histogram(custom_ranges[1], custom_ranges.back(),
-                custom_ranges.size()) {
-  DCHECK_GT(custom_ranges.size(), 1u);
-  DCHECK_EQ(custom_ranges[0], 0);
-}
-
-void CustomHistogram::InitializedCustomBucketRange(
-    const std::vector<Sample>& custom_ranges) {
-  DCHECK_GT(custom_ranges.size(), 1u);
-  DCHECK_EQ(custom_ranges[0], 0);
-  DCHECK_LE(custom_ranges.size(), bucket_count());
-  for (size_t index = 0; index < custom_ranges.size(); ++index)
-    SetBucketRange(index, custom_ranges[index]);
-  ResetRangeChecksum();
-}
-
-double CustomHistogram::GetBucketSize(Count current, size_t i) const {
-  return 1;
-}
-
 }  // namespace base
--- a/ipc/chromium/src/base/histogram.h
+++ b/ipc/chromium/src/base/histogram.h
@@ -66,17 +66,17 @@ class Histogram {
  public:
   typedef int Sample;  // Used for samples (and ranges of samples).
   typedef int Count;  // Used to count samples in a bucket.
   static const Sample kSampleType_MAX = INT_MAX;
   // Initialize maximum number of buckets in histograms as 16,384.
   static const size_t kBucketCount_MAX;
 
   typedef std::vector<Count> Counts;
-  typedef std::vector<Sample> Ranges;
+  typedef const Sample* Ranges;
 
   // These enums are used to facilitate deserialization of renderer histograms
   // into the browser.
   enum ClassType {
     HISTOGRAM,
     LINEAR_HISTOGRAM,
     BOOLEAN_HISTOGRAM,
     FLAG_HISTOGRAM,
@@ -268,21 +268,16 @@ class Histogram {
   virtual const std::string GetAsciiBucketRange(size_t it) const;
 
   //----------------------------------------------------------------------------
   // Methods to override to create thread safe histogram.
   //----------------------------------------------------------------------------
   // Update all our internal data, including histogram
   virtual void Accumulate(Sample value, Count count, size_t index);
 
-  //----------------------------------------------------------------------------
-  // Accessors for derived classes.
-  //----------------------------------------------------------------------------
-  void SetBucketRange(size_t i, Sample value);
-
   // Validate that ranges_ was created sensibly (top and bottom range
   // values relate properly to the declared_min_ and declared_max_)..
   bool ValidateBucketRanges() const;
 
   virtual uint32_t CalculateRangeChecksum() const;
 
   // Finally, provide the state that changes with the addition of each new
   // sample.
@@ -336,38 +331,33 @@ class LinearHistogram : public Histogram
  public:
   virtual ~LinearHistogram();
 
   /* minimum should start from 1. 0 is as minimum is invalid. 0 is an implicit
      default underflow bucket. */
   static Histogram* FactoryGet(Sample minimum,
                                Sample maximum,
                                size_t bucket_count,
-                               Flags flags);
-  static Histogram* FactoryTimeGet(TimeDelta minimum,
-                                   TimeDelta maximum,
-                                   size_t bucket_count,
-                                   Flags flags);
+                               Flags flags,
+                               const int* buckets);
 
   // Overridden from Histogram:
   virtual ClassType histogram_type() const;
 
   virtual void Accumulate(Sample value, Count count, size_t index);
 
   // Store a list of number/text values for use in rendering the histogram.
   // The last element in the array has a null in its "description" slot.
   virtual void SetRangeDescriptions(const DescriptionPair descriptions[]);
 
  protected:
   LinearHistogram(Sample minimum, Sample maximum, size_t bucket_count);
 
   LinearHistogram(TimeDelta minimum, TimeDelta maximum, size_t bucket_count);
 
-  // Initialize ranges_ mapping.
-  void InitializeBucketRange();
   virtual double GetBucketSize(Count current, size_t i) const;
 
   // If we have a description for a bucket, then return that.  Otherwise
   // let parent class provide a (numeric) description.
   virtual const std::string GetAsciiBucketRange(size_t i) const;
 
   // Skip printing of name for numeric range if we have a name (and if this is
   // an empty bucket).
@@ -383,17 +373,18 @@ class LinearHistogram : public Histogram
   DISALLOW_COPY_AND_ASSIGN(LinearHistogram);
 };
 
 //------------------------------------------------------------------------------
 
 // BooleanHistogram is a histogram for booleans.
 class BooleanHistogram : public LinearHistogram {
  public:
-  static Histogram* FactoryGet(Flags flags);
+  static Histogram* FactoryGet(Flags flags,
+                               const int* buckets);
 
   virtual ClassType histogram_type() const;
 
   virtual void AddBoolean(bool value);
 
   virtual void Accumulate(Sample value, Count count, size_t index);
 
  protected:
@@ -403,17 +394,18 @@ class BooleanHistogram : public LinearHi
 };
 
 //------------------------------------------------------------------------------
 
 // FlagHistogram is like boolean histogram, but only allows a single off/on value.
 class FlagHistogram : public BooleanHistogram
 {
 public:
-  static Histogram *FactoryGet(Flags flags);
+  static Histogram *FactoryGet(Flags flags,
+                               const int* buckets);
 
   virtual ClassType histogram_type() const;
 
   virtual void Accumulate(Sample value, Count count, size_t index);
 
   virtual void AddSampleSet(const SampleSet& sample);
 
   virtual void Clear();
@@ -424,47 +416,26 @@ private:
 
   DISALLOW_COPY_AND_ASSIGN(FlagHistogram);
 };
 
 // CountHistogram only allows a single monotic counter value.
 class CountHistogram : public LinearHistogram
 {
 public:
-  static Histogram *FactoryGet(Flags flags);
+  static Histogram *FactoryGet(Flags flags,
+                               const int* buckets);
 
   virtual ClassType histogram_type() const;
 
   virtual void Accumulate(Sample value, Count count, size_t index);
 
   virtual void AddSampleSet(const SampleSet& sample);
 
 private:
   explicit CountHistogram();
 
   DISALLOW_COPY_AND_ASSIGN(CountHistogram);
 };
 
-//------------------------------------------------------------------------------
-
-// CustomHistogram is a histogram for a set of custom integers.
-class CustomHistogram : public Histogram {
- public:
-
-  static Histogram* FactoryGet(const std::vector<Sample>& custom_ranges,
-                               Flags flags);
-
-  // Overridden from Histogram:
-  virtual ClassType histogram_type() const;
-
- protected:
-  explicit CustomHistogram(const std::vector<Sample>& custom_ranges);
-
-  // Initialize ranges_ mapping.
-  void InitializedCustomBucketRange(const std::vector<Sample>& custom_ranges);
-  virtual double GetBucketSize(Count current, size_t i) const;
-
-  DISALLOW_COPY_AND_ASSIGN(CustomHistogram);
-};
-
 }  // namespace base
 
 #endif  // BASE_METRICS_HISTOGRAM_H_
--- a/ipc/mscom/oop/moz.build
+++ b/ipc/mscom/oop/moz.build
@@ -25,15 +25,15 @@ OS_LIBS += [
     'shlwapi',
 ]
 
 LIBRARY_DEFINES['UNICODE'] = True
 LIBRARY_DEFINES['_UNICODE'] = True
 LIBRARY_DEFINES['MOZ_NO_MOZALLOC'] = True
 LIBRARY_DEFINES['MOZ_MSCOM_REMARSHAL_NO_HANDLER'] = True
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 NO_EXPAND_LIBS = True
 FORCE_STATIC_LIB = True
 
 # This DLL may be loaded into other processes, so we need static libs for
 # Windows 7 and Windows 8.
 USE_STATIC_LIBS = True
--- a/js/src/make-source-package.sh
+++ b/js/src/make-source-package.sh
@@ -157,17 +157,16 @@ case $cmd in
         ${TOPSRCDIR}/mozglue/moz.build \
         ${tgtpath}/mozglue
     ${MKDIR} -p ${tgtpath}/memory
     cp -pPR \
         ${TOPSRCDIR}/memory/moz.build \
         ${TOPSRCDIR}/memory/build \
         ${TOPSRCDIR}/memory/fallible \
         ${TOPSRCDIR}/memory/mozalloc \
-        ${TOPSRCDIR}/memory/mozjemalloc \
         ${tgtpath}/memory
 
     # remove *.pyc and *.pyo files if any
     find ${tgtpath} -type f -name "*.pyc" -o -name "*.pyo" |xargs rm -f
 
     # Remove non-JS Cargo.toml files (for example, the invalid Cargo.toml files
     # used for some testing).
     find ${tgtpath} -type f -name Cargo.toml | grep -v js | xargs rm -f
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -254,17 +254,17 @@ typedef mozilla::Variant<ScriptSourceObj
 // Either a AbstractFramePtr, for ordinary JS, or a wasm::DebugFrame,
 // for synthesized frame of a wasm code.
 typedef mozilla::Variant<AbstractFramePtr, wasm::DebugFrame*> DebuggerFrameReferent;
 
 class Debugger : private mozilla::LinkedListElement<Debugger>
 {
     friend class Breakpoint;
     friend class DebuggerMemory;
-    friend struct JSRuntime::GlobalObjectWatchersSiblingAccess<Debugger>;
+    friend struct JSRuntime::GlobalObjectWatchersLinkAccess<Debugger>;
     friend class SavedStacks;
     friend class ScriptedOnStepHandler;
     friend class ScriptedOnPopHandler;
     friend class mozilla::LinkedListElement<Debugger>;
     friend class mozilla::LinkedList<Debugger>;
     friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
     friend bool (::JS::dbg::IsDebugger)(JSObject&);
     friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, AutoObjectVector&);
@@ -384,36 +384,27 @@ class Debugger : private mozilla::Linked
     js::GCPtrObject uncaughtExceptionHook; /* Strong reference. */
     bool enabled;
     bool allowUnobservedAsmJS;
     bool allowWasmBinarySource;
 
     // Whether to enable code coverage on the Debuggee.
     bool collectCoverageInfo;
 
-    template<typename T>
-    struct DebuggerSiblingAccess {
-      static T* GetNext(T* elm) {
-        return elm->debuggerLink.mNext;
-      }
-      static void SetNext(T* elm, T* next) {
-        elm->debuggerLink.mNext = next;
-      }
-      static T* GetPrev(T* elm) {
-        return elm->debuggerLink.mPrev;
-      }
-      static void SetPrev(T* elm, T* prev) {
-        elm->debuggerLink.mPrev = prev;
+    template <typename T>
+    struct DebuggerLinkAccess {
+      static mozilla::DoublyLinkedListElement<T>& Get(T* aThis) {
+        return aThis->debuggerLink;
       }
     };
 
     // List of all js::Breakpoints in this debugger.
     using BreakpointList =
         mozilla::DoublyLinkedList<js::Breakpoint,
-                                  DebuggerSiblingAccess<js::Breakpoint>>;
+                                  DebuggerLinkAccess<js::Breakpoint>>;
     BreakpointList breakpoints;
 
     // The set of GC numbers for which one or more of this Debugger's observed
     // debuggees participated in.
     using GCNumberSet = HashSet<uint64_t, DefaultHasher<uint64_t>, RuntimeAllocPolicy>;
     GCNumberSet observedGCs;
 
     using AllocationsLog = js::TraceableFifo<AllocationsLogEntry>;
@@ -1590,36 +1581,27 @@ class BreakpointSite {
     friend class Debugger;
 
   public:
     enum class Type { JS, Wasm };
 
   private:
     Type type_;
 
-    template<typename T>
-    struct SiteSiblingAccess {
-      static T* GetNext(T* elm) {
-        return elm->siteLink.mNext;
-      }
-      static void SetNext(T* elm, T* next) {
-        elm->siteLink.mNext = next;
-      }
-      static T* GetPrev(T* elm) {
-        return elm->siteLink.mPrev;
-      }
-      static void SetPrev(T* elm, T* prev) {
-        elm->siteLink.mPrev = prev;
+    template <typename T>
+    struct SiteLinkAccess {
+      static mozilla::DoublyLinkedListElement<T>& Get(T* aThis) {
+        return aThis->siteLink;
       }
     };
 
     // List of all js::Breakpoints at this instruction.
     using BreakpointList =
         mozilla::DoublyLinkedList<js::Breakpoint,
-                                  SiteSiblingAccess<js::Breakpoint>>;
+                                  SiteLinkAccess<js::Breakpoint>>;
     BreakpointList breakpoints;
     size_t enabledCount;  /* number of breakpoints in the list that are enabled */
 
   protected:
     virtual void recompile(FreeOp* fop) = 0;
     bool isEmpty() const;
     inline bool isEnabled() const { return enabledCount > 0; }
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -577,34 +577,25 @@ struct JSRuntime : public js::MallocProv
     js::ActiveThreadData<mozilla::LinkedList<JS::detail::WeakCacheBase>> weakCaches_;
   public:
     mozilla::LinkedList<JS::detail::WeakCacheBase>& weakCaches() { return weakCaches_.ref(); }
     void registerWeakCache(JS::detail::WeakCacheBase* cachep) {
         weakCaches().insertBack(cachep);
     }
 
     template <typename T>
-    struct GlobalObjectWatchersSiblingAccess {
-      static T* GetNext(T* elm) {
-        return elm->onNewGlobalObjectWatchersLink.mNext;
-      }
-      static void SetNext(T* elm, T* next) {
-        elm->onNewGlobalObjectWatchersLink.mNext = next;
-      }
-      static T* GetPrev(T* elm) {
-        return elm->onNewGlobalObjectWatchersLink.mPrev;
-      }
-      static void SetPrev(T* elm, T* prev) {
-        elm->onNewGlobalObjectWatchersLink.mPrev = prev;
+    struct GlobalObjectWatchersLinkAccess {
+      static mozilla::DoublyLinkedListElement<T>& Get(T* aThis) {
+        return aThis->onNewGlobalObjectWatchersLink;
       }
     };
 
     using WatchersList =
         mozilla::DoublyLinkedList<js::Debugger,
-                                  GlobalObjectWatchersSiblingAccess<js::Debugger>>;
+                                  GlobalObjectWatchersLinkAccess<js::Debugger>>;
   private:
     /*
      * List of all enabled Debuggers that have onNewGlobalObject handler
      * methods established.
      */
     js::ActiveThreadData<WatchersList> onNewGlobalObjectWatchers_;
 
   public:
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -87,17 +87,16 @@ const char* const XPCJSRuntime::mStrings
     "Components",           // IDX_COMPONENTS
     "wrappedJSObject",      // IDX_WRAPPED_JSOBJECT
     "Object",               // IDX_OBJECT
     "Function",             // IDX_FUNCTION
     "prototype",            // IDX_PROTOTYPE
     "createInstance",       // IDX_CREATE_INSTANCE
     "item",                 // IDX_ITEM
     "__proto__",            // IDX_PROTO
-    "__exposedProps__",     // IDX_EXPOSEDPROPS
     "eval",                 // IDX_EVAL
     "controllers",          // IDX_CONTROLLERS
     "Controllers",          // IDX_CONTROLLERS_CLASS
     "realFrameElement",     // IDX_REALFRAMEELEMENT
     "length",               // IDX_LENGTH
     "name",                 // IDX_NAME
     "undefined",            // IDX_UNDEFINED
     "",                     // IDX_EMPTYSTRING
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -453,17 +453,16 @@ public:
         IDX_COMPONENTS              ,
         IDX_WRAPPED_JSOBJECT        ,
         IDX_OBJECT                  ,
         IDX_FUNCTION                ,
         IDX_PROTOTYPE               ,
         IDX_CREATE_INSTANCE         ,
         IDX_ITEM                    ,
         IDX_PROTO                   ,
-        IDX_EXPOSEDPROPS            ,
         IDX_EVAL                    ,
         IDX_CONTROLLERS             ,
         IDX_CONTROLLERS_CLASS       ,
         IDX_REALFRAMEELEMENT        ,
         IDX_LENGTH                  ,
         IDX_NAME                    ,
         IDX_UNDEFINED               ,
         IDX_EMPTYSTRING             ,
--- a/js/xpconnect/tests/chrome/test_bug1065185.html
+++ b/js/xpconnect/tests/chrome/test_bug1065185.html
@@ -20,21 +20,21 @@ https://bugzilla.mozilla.org/show_bug.cg
     SimpleTest.monitorConsole(() => SimpleTest.executeSoon(() => window[0].location.reload()), messages, /* forbidUnexpected = */ true);
   }
   function endMonitor() {
     SimpleTest.executeSoon(SimpleTest.endMonitorConsole.bind(SimpleTest));
   }
 
   var gLoadCount = 0;
   function loaded() {
-    switch(++gLoadCount) {
+    switch(gLoadCount++) {
       case 0:
-        doMonitor([]);
+        doMonitor([/access to property "a"/i]);
         window[0].wrappedJSObject.probe = { a: 2, __exposedProps__: { 'a': 'r' } };
-        is(window[0].eval('probe.a'), 2, "Accessed exposed prop");
+        is(window[0].eval('probe.a'), undefined, "Accessed exposed prop");
         endMonitor();
         break;
       case 1:
         doMonitor([/access to property "a"/i]);
         window[0].wrappedJSObject.probe = { a: 2 };
         is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined");
         is(window[0].eval('probe.a'), undefined, "Non-exposed prop undefined again");
         endMonitor();
--- a/js/xpconnect/tests/chrome/test_cows.xul
+++ b/js/xpconnect/tests/chrome/test_cows.xul
@@ -44,23 +44,16 @@ function getCOW(x) {
 
 // Give the sandbox a way to create ChromeObjectWrapped objects.
 sandbox.getCOW = getCOW;
 
 // Define test API functions in the sandbox.
 const TEST_API = ['is', 'isnot', 'ok', 'todo_is', 'todo_isnot', 'todo'];
 TEST_API.forEach(function(name) { sandbox[name] = window[name]; });
 
-sandbox.alienObject = {
-  __exposedProps__: {funProp: 'r'},
-  funProp: function foo(x) {
-    return x + 1;
-  }
-};
-
 sandbox.chromeGet = function (obj, prop) { return obj[prop]; };
 
 function COWTests() {
     function getNames(cow) {
         let names = [];
         for (let name in cow) {
             names.push(name);
         }
@@ -69,27 +62,16 @@ function COWTests() {
 
     // This function is actually decompiled and run inside a
     // sandbox with content privileges.
 
     // TODO: This could use some refactoring; creating helper
     // functions like assertIsWritable(myObj, 'someproperty') might
     // be useful.
 
-    function isProp(obj, propName, value, desc) {
-      try {
-          is(obj[propName], value, "getting " + propName + " on " + desc);
-          ok(propName in obj,
-             propName + " on " + desc + " should exist");
-          ok(Object.hasOwnProperty.call(obj, propName),
-             propName + " on " + desc + " should exist");
-      } catch (e) {
-          ok(false, "getting " + propName + " on " + desc + " threw " + e);
-      }
-    }
     function isPropHidden(obj, propName, desc) {
       try {
           is(obj[propName], undefined,
              "getting " + propName + " on " + desc + " should return undefined");
           ok(!(propName in obj),
              propName + " on " + desc + " should act as if it doesn't exist");
           ok(!Object.hasOwnProperty.call(obj, propName),
              propName + " on " + desc + " should act as if it doesn't exist");
@@ -98,17 +80,17 @@ function COWTests() {
       }
     }
 
     const PROPS_TO_TEST = ['foo', 'bar', 'prototype'];
 
     var empty = {};
     var nonempty = {foo: 42, bar: 33};
     is(getCOW(empty).foo, undefined,
-       "shouldn't throw when accessing exposed properties that doesn't exist");
+       "shouldn't throw when accessing exposed properties that don't exist");
 
     PROPS_TO_TEST.forEach(function(name) {
         isPropHidden(getCOW(nonempty), name, "object without exposedProps");
     });
 
     // Test function objects.
     var func = function(x) { return 42; };
     func.__exposedProps__ = { foo: "r" };
@@ -130,63 +112,50 @@ function COWTests() {
     });
     is(getNames(strictCOW).length, 0,
        "object with empty exposedProps shouldn't have any properties");
 
     // Test object with one exposed property
     var strict = { __exposedProps__: { foo: "r" }, foo: "foo property" };
     var strictCOWr = getCOW(strict);
     PROPS_TO_TEST.forEach(function(name) {
-        if (name == "foo") {
-            isProp(strictCOWr, name, "foo property",
-                   "object with exposed 'foo'");
-        }
-        else {
-            isPropHidden(strictCOW, name, "object with exposed 'foo'");
-        }
+        isPropHidden(strictCOW, name, "object with exposed 'foo'");
     });
-    is(getNames(strictCOWr).length, 1,
-       "object with exposedProps only enumerate exposed props");
-    is(getNames(strictCOWr)[0], "foo",
-       "object with exposedProps only enumerate exposed props");
+    is(getNames(strictCOWr).length, 0,
+       "exposed props does not enumerate anything");
+    is(getNames(strictCOWr)[0], undefined,
+       "exposed props does not enumerate anything");
 
     // Test writable property
     var writable = getCOW({ __exposedProps__: {foo: 'w'}});
     try {
         ok(!("foo" in writable),
            "non-existing write-only property shouldn't exist");
         writable.foo = 5;
-        is(chromeGet(writable, "foo"), 5, "writing to a write-only exposed prop works");
-        todo("foo" in writable,
-             "existing write-only property should exist");
+        ok(false, "writing to a write-only exposed prop should throw");
     } catch (e) {
-        ok(false, "writing to a write-only exposed prop shouldn't throw " + e);
+        ok(/Permission denied/.test(e),
+           "writing to a write-only exposed prop should throw the right error");
     }
-    try {
-        writable.foo;
-        todo(false, "reading from a write-only exposed prop should throw");
-    } catch (e) {
-        todo(/Permission denied/.test(e),
-             "reading from a write-only exposed prop should throw");
-    }
+    is(writable.foo, undefined,
+       "reading from a write-only exposed prop should return undefined");
     try {
         delete writable.foo;
-        is(chromeGet(writable, "foo"), undefined,
-           "deleting a write-only exposed prop works");
+        ok(false, "deleting a write-only exposed prop should throw");
     } catch (e) {
-        ok(false, "deleting a write-only exposed prop shouldn't throw " + e);
+        ok(true, "deleting a write-only exposed prop should throw " + e);
     }
 
     // Test readable property
     var readable = { __exposedProps__: {foo: 'r'},
                      foo: 5,
                      bar: 6 };
     try {
-        isProp(getCOW(readable), "foo", 5,
-               "reading from a readable exposed prop works");
+        isPropHidden(getCOW(readable), "foo", undefined,
+                     "reading from a readable exposed prop shouldn't work");
     } catch (e) {
         ok(false, "reading from a readable exposed prop shouldn't throw " + e);
     }
     try {
         getCOW(readable).foo = 1;
         ok(false, "writing to a read-only exposed prop should fail");
     } catch (e) {
         ok(/Permission denied/.test(e),
@@ -197,44 +166,39 @@ function COWTests() {
         ok(false, "deleting a read-only exposed prop shouldn't work");
     } catch (e) {
         ok(/Permission denied/.test(e),
            "deleting a read-only exposed prop should throw error");
     }
 
     try {
         var props = getNames(getCOW(readable));
-        is(props.length, 1, "COW w/ one exposed prop should enumerate once");
-        is(props[0], 'foo', "COW w/ one exposed prop should enumerate it");
+        is(props.length, 0, "COW w/ one exposed prop should not enumerate");
     } catch (e) {
         ok(false, "COW w/ a readable prop should not raise exc " +
                   "on enumeration: " + e);
     }
 
     // Test read/write property
     var readwrite = getCOW({ __exposedProps__: {foo: 'rw'}});
     try {
         ok(!("foo" in readwrite),
            "non-existing readwrite property shouldn't exist");
         readwrite.foo = 5;
-        is(readwrite.foo, 5, "writing to a readwrite exposed prop looks like it worked");
-        is(chromeGet(readwrite, "foo"), 5, "writing to a readwrite exposed prop works");
-        ok("foo" in readwrite,
-           "existing readwrite property should exist");
+        ok(false, "writing to a readwrite exposed prop should throw");
     } catch (e) {
-        ok(false, "writing to a readwrite exposed prop shouldn't throw " + e);
+        ok(/Permission denied/.test(e),
+           "writing to a readwrite exposed prop should throw the right error");
     }
     try {
         delete readwrite.foo;
-        is(readwrite.foo, undefined, "deleting readwrite prop looks like it worked");
-        ok(!("foo" in readwrite), "deleting readwrite prop looks like it really worked");
-        is(chromeGet(readwrite, "foo"), undefined,
-           "deleting a readwrite exposed prop works");
+        ok(false, "deleting a readwrite prop should throw");
     } catch (e) {
-        ok(false, "deleting a readwrite exposed prop shouldn't throw " + e);
+        ok(/Permission denied/.test(e),
+           "deleting a readwrite exposed prop should throw the right error");
     }
 
     // Readables and functions
     try {
         var COWFunc = getCOW((function() { return 5; }));
         is(COWFunc(), 5, "COWed functions should be callable");
     } catch (e) {
         todo(false, "COWed functions should not raise " + e);
--- a/js/xpconnect/tests/chrome/test_exposeInDerived.xul
+++ b/js/xpconnect/tests/chrome/test_exposeInDerived.xul
@@ -12,35 +12,36 @@ https://bugzilla.mozilla.org/show_bug.cg
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=804630"
      target="_blank">Mozilla Bug 804630</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
-  /** Test to make sure that COWed objects can expose properties from their prototypes. **/
+  /** Test to make sure that COWed objects can't expose properties from their prototypes. **/
   const Cu = Components.utils;
 
   // Set up the sandbox.
   var sb = new Cu.Sandbox('http://www.example.com');
   sb.ok = ok;
   sb.is = is;
 
-  // Make a chrome object that exposes objects off its prototype.
+  // Make a chrome object that tries to expose objects off its prototype.
   sb.proto = { read: 42, readWrite: 32, __exposedProps__: {} };
   sb.obj = { __exposedProps__: { read: 'r', readWrite: 'rw' } };
   sb.obj.__proto__ = sb.proto;
 
   // Make sure we can't access any of the properties on the prototype directly.
   Cu.evalInSandbox('is(proto.read, undefined, "proto.read inaccessible");', sb);
   Cu.evalInSandbox('var wrote = false; ' +
                    'try { proto.readWrite = 12; wrote = true; } catch(e) {} ' +
                    ' ok(!wrote, "Should not write proto property");', sb);
 
-  // Make sure we can access the exposed properties via the derived object.
-  Cu.evalInSandbox('is(obj.read, 42, "obj.read accessible");', sb);
-  Cu.evalInSandbox('is(obj.readWrite, 32, "obj.readWrite is readable");', sb);
-  Cu.evalInSandbox('obj.readWrite = 8; is(obj.readWrite, 8, "obj.readWrite is writable");', sb);
+  // Make sure we can't access the exposed properties via the derived object.
+  Cu.evalInSandbox('is(obj.read, undefined, "obj.read inaccessible");', sb);
+  Cu.evalInSandbox('is(obj.readWrite, undefined, "obj.readWrite is not readable");', sb);
+  Cu.evalInSandbox('try { obj.readWrite = 8; ok(false, "obj.readWrite is not writable"); } catch (e) {};',
+                   sb);
 
   ]]>
   </script>
 </window>
--- a/js/xpconnect/tests/unit/test_bug1082450.js
+++ b/js/xpconnect/tests/unit/test_bug1082450.js
@@ -1,18 +1,18 @@
 const Cu = Components.utils;
 function run_test() {
 
   var sb = new Cu.Sandbox('http://www.example.com');
   function checkThrows(str, rgxp) {
     try {
       sb.eval(str);
-      do_check_true(false);
+      do_check_true(false, "eval should have thrown");
     } catch (e) {
-      do_check_true(rgxp.test(e));
+      do_check_true(rgxp.test(e), "error message should match");
     }
   }
 
   sb.exposed = {
     get getterProp() { return 42; },
     set setterProp(x) { },
     get getterSetterProp() { return 42; },
     set getterSetterProp(x) { },
@@ -24,17 +24,17 @@ function run_test() {
                         setterProp : 'w',
                         getterSetterProp: 'rw',
                         simpleValueProp: 'r',
                         objectValueProp: 'r',
                         contentCallableValueProp: 'r',
                         chromeCallableValueProp: 'r' }
   };
 
-  do_check_eq(sb.eval('exposed.simpleValueProp'), 42);
-  do_check_eq(sb.eval('exposed.objectValueProp.val'), 42);
-  checkThrows('exposed.getterProp;', /privileged accessor/i);
-  checkThrows('exposed.setterProp = 42;', /privileged accessor/i);
-  checkThrows('exposed.getterSetterProp;', /privileged accessor/i);
-  checkThrows('exposed.getterSetterProp = 42;', /privileged accessor/i);
-  do_check_eq(sb.eval('exposed.contentCallableValueProp()'), 42);
-  checkThrows('exposed.chromeCallableValueProp();', /privileged or cross-origin callable/i);
+  do_check_eq(sb.eval('exposed.simpleValueProp'), undefined);
+  do_check_eq(sb.eval('exposed.objectValueProp'), undefined);
+  do_check_eq(sb.eval('exposed.getterProp;'), undefined);
+  do_check_eq(sb.eval('exposed.getterSetterProp;'), undefined);
+  checkThrows('exposed.setterProp = 42;', /Permission denied/i);
+  checkThrows('exposed.getterSetterProp = 42;', /Permission denied/i);
+  do_check_eq(sb.eval('exposed.contentCallableValueProp'), undefined);
+  checkThrows('exposed.chromeCallableValueProp();', /is not a function/i);
 }
--- a/js/xpconnect/tests/unit/test_bug780370.js
+++ b/js/xpconnect/tests/unit/test_bug780370.js
@@ -9,15 +9,10 @@ const Cu = Components.utils;
 // Use a COW to expose a function from a standard prototype, and make we deny
 // access to it.
 
 function run_test()
 {
   var sb = Cu.Sandbox("http://www.example.com");
   sb.obj = { foo: 42, __exposedProps__: { hasOwnProperty: 'r' } };
   do_check_eq(Cu.evalInSandbox('typeof obj.foo', sb), 'undefined', "COW works as expected");
-  try {
-    Cu.evalInSandbox('obj.hasOwnProperty', sb);
-    do_check_true(false);
-  } catch (e) {
-    do_check_true(/privileged or cross-origin callable/i.test(e));
-  }
+  do_check_eq(Cu.evalInSandbox('obj.hasOwnProperty', sb), undefined);
 }
--- a/js/xpconnect/tests/unit/test_bug813901.js
+++ b/js/xpconnect/tests/unit/test_bug813901.js
@@ -16,10 +16,10 @@ function checkThrows(expression, sb, reg
 
 function run_test() {
 
   var sb = new Cu.Sandbox('http://www.example.org');
   sb.obj = {foo: 2};
   checkThrows('obj.foo = 3;', sb, /denied/);
   Cu.evalInSandbox("var p = {__exposedProps__: {foo: 'rw'}};", sb);
   sb.obj.__proto__ = sb.p;
-  checkThrows('obj.foo = 4;', sb, /__exposedProps__/);
+  checkThrows('obj.foo = 4;', sb, /denied/);
 }
--- a/js/xpconnect/tests/unit/test_bug853709.js
+++ b/js/xpconnect/tests/unit/test_bug853709.js
@@ -3,28 +3,28 @@ const Cu = Components.utils;
 function setupChromeSandbox() {
   this.chromeObj = {a: 2, __exposedProps__: {a: "rw", b: "rw"} };
   this.chromeArr = [4, 2, 1];
 }
 
 function checkDefineThrows(sb, obj, prop, desc) {
   var result = Cu.evalInSandbox('(function() { try { Object.defineProperty(' + obj + ', "' + prop + '", ' + desc.toSource() + '); return "nothrow"; } catch (e) { return e.toString(); }})();', sb);
   do_check_neq(result, 'nothrow');
-  do_check_true(!!/denied/.exec(result));
+  do_check_true(!!/denied|prohibited/.exec(result));
   do_check_true(result.indexOf(prop) != -1); // Make sure the prop name is in the error message.
 }
 
 function run_test() {
   var chromeSB = new Cu.Sandbox(this);
   var contentSB = new Cu.Sandbox('http://www.example.org');
   Cu.evalInSandbox('(' + setupChromeSandbox.toSource() + ')()', chromeSB);
   contentSB.chromeObj = chromeSB.chromeObj;
   contentSB.chromeArr = chromeSB.chromeArr;
 
-  do_check_eq(Cu.evalInSandbox('chromeObj.a', contentSB), 2);
+  do_check_eq(Cu.evalInSandbox('chromeObj.a', contentSB), undefined);
   try {
     Cu.evalInSandbox('chromeArr[1]', contentSB);
     do_check_true(false);
   } catch (e) { do_check_true(/denied|insecure/.test(e)); }
 
   checkDefineThrows(contentSB, 'chromeObj', 'a', {get: function() { return 2; }});
   checkDefineThrows(contentSB, 'chromeObj', 'a', {configurable: true, get: function() { return 2; }});
   checkDefineThrows(contentSB, 'chromeObj', 'b', {configurable: true, get: function() { return 2; }, set: function() {}});
--- a/js/xpconnect/tests/unit/test_bug854558.js
+++ b/js/xpconnect/tests/unit/test_bug854558.js
@@ -2,10 +2,10 @@ const Cu = Components.utils;
 function run_test() {
 
   var chromeSB = new Cu.Sandbox(this);
   var contentSB = new Cu.Sandbox('http://www.example.com');
   Cu.evalInSandbox('this.foo = {a: 2}', chromeSB);
   contentSB.foo = chromeSB.foo;
   do_check_eq(Cu.evalInSandbox('foo.a', contentSB), undefined, "Default deny with no __exposedProps__");
   Cu.evalInSandbox('this.foo.__exposedProps__ = {a: "r"}', chromeSB);
-  do_check_eq(Cu.evalInSandbox('foo.a', contentSB), 2, "works with __exposedProps__");
+  do_check_eq(Cu.evalInSandbox('foo.a', contentSB), undefined, "Still not allowed with __exposedProps__");
 }
--- a/js/xpconnect/tests/unit/test_bug930091.js
+++ b/js/xpconnect/tests/unit/test_bug930091.js
@@ -1,16 +1,16 @@
 const Cu = Components.utils;
 
 function checkThrows(fn) {
   try {
     fn();
     ok(false, "Should have thrown");
   } catch (e) {
-    do_check_true(/denied|insecure/.test(e));
+    do_check_true(/denied|insecure|prohibited/.test(e));
   }
 }
 
 function run_test() {
   var xosb = new Cu.Sandbox('http://www.example.org');
   var sb = new Cu.Sandbox('http://www.example.com');
   sb.do_check_true = do_check_true;
   sb.fun = function() { ok(false, "Shouldn't ever reach me"); };
--- a/js/xpconnect/wrappers/AccessCheck.cpp
+++ b/js/xpconnect/wrappers/AccessCheck.cpp
@@ -247,30 +247,16 @@ AccessCheck::checkPassToPrivilegedCode(J
     // pass any objects at all to CPOWs.
     if (mozilla::jsipc::IsWrappedCPOW(obj) &&
         js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) &&
         XRE_IsParentProcess())
     {
         return true;
     }
 
-    // COWs are fine to pass to chrome if and only if they have __exposedProps__,
-    // since presumably content should never have a reason to pass an opaque
-    // object back to chrome.
-    if (AccessCheck::isChrome(js::UncheckedUnwrap(wrapper)) && WrapperFactory::IsCOW(obj)) {
-        RootedObject target(cx, js::UncheckedUnwrap(obj));
-        JSAutoCompartment ac(cx, target);
-        RootedId id(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
-        bool found = false;
-        if (!JS_HasPropertyById(cx, target, id, &found))
-            return false;
-        if (found)
-            return true;
-    }
-
     // Same-origin wrappers are fine.
     if (AccessCheck::wrapperSubsumes(obj))
         return true;
 
     // Badness.
     JS_ReportErrorASCII(cx, "Permission denied to pass object to privileged code");
     return false;
 }
@@ -318,184 +304,19 @@ AccessCheck::reportCrossOriginDenial(JSC
                   NS_ConvertUTF16toUTF8(propName) +
                   NS_LITERAL_CSTRING(" on cross-origin object");
     }
     ErrorResult rv;
     rv.ThrowDOMException(NS_ERROR_DOM_SECURITY_ERR, message);
     MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(cx));
 }
 
-enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
-
-static void
-EnterAndThrowASCII(JSContext* cx, JSObject* wrapper, const char* msg)
-{
-    JSAutoCompartment ac(cx, wrapper);
-    JS_ReportErrorASCII(cx, "%s", msg);
-}
-
 bool
-ExposedPropertiesOnly::check(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act)
-{
-    RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));
-
-    if (act == Wrapper::CALL)
-        return false;
-
-    // For the case of getting a property descriptor, we allow if either GET or SET
-    // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
-    if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
-        return check(cx, wrapper, id, Wrapper::GET) ||
-               check(cx, wrapper, id, Wrapper::SET);
-    }
-
-    RootedId exposedPropsId(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
-
-    // We need to enter the wrappee's compartment to look at __exposedProps__,
-    // but we want to be in the wrapper's compartment if we call Deny().
-    //
-    // Unfortunately, |cx| can be in either compartment when we call ::check. :-(
-    JSAutoCompartment ac(cx, wrappedObject);
-
-    bool found = false;
-    if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
-        return false;
-
-    // If no __exposedProps__ existed, deny access.
-    if (!found) {
-        // Previously we automatically granted access to indexed properties and
-        // .length for Array COWs. We're not doing that anymore, so make sure to
-        // let people know what's going on.
-        bool isArray;
-        if (!JS_IsArrayObject(cx, wrappedObject, &isArray))
-            return false;
-        if (!isArray)
-            isArray = JS_IsTypedArrayObject(wrappedObject);
-        bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0;
-        bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) &&
-                                     JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length");
-        if (isIndexedAccessOnArray || isLengthAccessOnArray) {
-            JSAutoCompartment ac2(cx, wrapper);
-            ReportWrapperDenial(cx, id, WrapperDenialForCOW,
-                                "Access to elements and length of privileged Array not permitted");
-        }
-
-        return false;
-    }
-
-    if (id == JSID_VOID)
-        return true;
-
-    Rooted<PropertyDescriptor> desc(cx);
-    if (!JS_GetPropertyDescriptorById(cx, wrappedObject, exposedPropsId, &desc))
-        return false;
-
-    if (!desc.object())
-        return false;
-
-    if (desc.hasGetterOrSetter()) {
-        EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be a value property");
-        return false;
-    }
-
-    RootedValue exposedProps(cx, desc.value());
-    if (exposedProps.isNullOrUndefined())
-        return false;
-
-    if (!exposedProps.isObject()) {
-        EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
-        return false;
-    }
-
-    RootedObject hallpass(cx, &exposedProps.toObject());
-
-    if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
-        EnterAndThrowASCII(cx, wrapper, "Invalid __exposedProps__");
-        return false;
-    }
-
-    Access access = NO_ACCESS;
-
-    if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
-        return false; // Error
-    }
-    if (!desc.object() || !desc.enumerable())
-        return false;
-
-    if (!desc.value().isString()) {
-        EnterAndThrowASCII(cx, wrapper, "property must be a string");
-        return false;
-    }
-
-    JSFlatString* flat = JS_FlattenString(cx, desc.value().toString());
-    if (!flat)
-        return false;
-
-    size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat));
-
-    for (size_t i = 0; i < length; ++i) {
-        char16_t ch = JS_GetFlatStringCharAt(flat, i);
-        switch (ch) {
-        case 'r':
-            if (access & READ) {
-                EnterAndThrowASCII(cx, wrapper, "duplicate 'readable' property flag");
-                return false;
-            }
-            access = Access(access | READ);
-            break;
-
-        case 'w':
-            if (access & WRITE) {
-                EnterAndThrowASCII(cx, wrapper, "duplicate 'writable' property flag");
-                return false;
-            }
-            access = Access(access | WRITE);
-            break;
-
-        default:
-            EnterAndThrowASCII(cx, wrapper, "properties can only be readable or read and writable");
-            return false;
-        }
-    }
-
-    if (access == NO_ACCESS) {
-        EnterAndThrowASCII(cx, wrapper, "specified properties must have a permission bit set");
-        return false;
-    }
-
-    if ((act == Wrapper::SET && !(access & WRITE)) ||
-        (act != Wrapper::SET && !(access & READ))) {
-        return false;
-    }
-
-    // Inspect the property on the underlying object to check for red flags.
-    if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc))
-        return false;
-
-    // Reject accessor properties.
-    if (desc.hasGetterOrSetter()) {
-        EnterAndThrowASCII(cx, wrapper, "Exposing privileged accessor properties is prohibited");
-        return false;
-    }
-
-    // Reject privileged or cross-origin callables.
-    if (desc.value().isObject()) {
-        RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject()));
-        if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) {
-            EnterAndThrowASCII(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited");
-            return false;
-        }
-    }
-
-    return true;
-}
-
-bool
-ExposedPropertiesOnly::deny(JSContext* cx, js::Wrapper::Action act, HandleId id,
-                            bool mayThrow)
+OpaqueWithSilentFailing::deny(JSContext* cx, js::Wrapper::Action act, HandleId id,
+                              bool mayThrow)
 {
     // Fail silently for GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR.
     if (act == js::Wrapper::GET || act == js::Wrapper::ENUMERATE ||
         act == js::Wrapper::GET_PROPERTY_DESCRIPTOR)
     {
         // Note that ReportWrapperDenial doesn't do any _exception_ reporting,
         // so we want to do this regardless of the value of mayThrow.
         return ReportWrapperDenial(cx, id, WrapperDenialForCOW,
--- a/js/xpconnect/wrappers/AccessCheck.h
+++ b/js/xpconnect/wrappers/AccessCheck.h
@@ -99,20 +99,25 @@ struct CrossOriginAccessiblePropertiesOn
                                                  NS_LITERAL_CSTRING("access"));
         return false;
     }
     static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
         return false;
     }
 };
 
-// This policy only permits access to properties if they appear in the
-// objects exposed properties list.
-struct ExposedPropertiesOnly : public Policy {
-    static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act);
+// This class used to support permitting access to properties if they
+// appeared in an access list on the object, but now it acts like an
+// Opaque wrapper, with the exception that it fails silently for GET,
+// ENUMERATE, and GET_PROPERTY_DESCRIPTOR. This is done for backwards
+// compatibility. See bug 1397513.
+struct OpaqueWithSilentFailing : public Policy {
+    static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act) {
+        return false;
+    }
 
     static bool deny(JSContext* cx, js::Wrapper::Action act, JS::HandleId id,
                      bool mayThrow);
     static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) {
         return false;
     }
 };
 
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.h
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.h
@@ -8,24 +8,24 @@
 #define __ChromeObjectWrapper_h__
 
 #include "mozilla/Attributes.h"
 
 #include "FilteringWrapper.h"
 
 namespace xpc {
 
-struct ExposedPropertiesOnly;
+struct OpaqueWithSilentFailing;
 
 // When a vanilla chrome JS object is exposed to content, we use a wrapper that
-// supports __exposedProps__ for legacy reasons. For extra security, we override
-// the traps that allow content to pass an object to chrome, and perform extra
-// security checks on them.
+// fails silently on GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR for legacy
+// reasons. For extra security, we override the traps that allow content to pass
+// an object to chrome, and perform extra security checks on them.
 #define ChromeObjectWrapperBase \
-  FilteringWrapper<js::CrossCompartmentSecurityWrapper, ExposedPropertiesOnly>
+  FilteringWrapper<js::CrossCompartmentSecurityWrapper, OpaqueWithSilentFailing>
 
 class ChromeObjectWrapper : public ChromeObjectWrapperBase
 {
   public:
     constexpr ChromeObjectWrapper() : ChromeObjectWrapperBase(0) {}
 
     virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
                                 JS::Handle<jsid> id,
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -509,18 +509,18 @@ WrapperFactory::Rewrap(JSContext* cx, Ha
         // here, but only in the content process.
         if ((IdentifyStandardInstance(obj) == JSProto_Function ||
             (jsipc::IsCPOW(obj) && JS::IsCallable(obj) &&
              XRE_IsContentProcess())))
         {
             wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
         }
 
-        // For Vanilla JSObjects exposed from chrome to content, we use a wrapper
-        // that supports __exposedProps__. We'd like to get rid of these eventually,
+        // For vanilla JSObjects exposed from chrome to content, we use a wrapper
+        // that fails silently in a few cases. We'd like to get rid of this eventually,
         // but in their current form they don't cause much trouble.
         else if (IdentifyStandardInstance(obj) == JSProto_Object) {
             wrapper = &ChromeObjectWrapper::singleton;
         }
 
         // Otherwise we get an opaque wrapper.
         else {
             wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -286,17 +286,17 @@ ReportWrapperDenial(JSContext* cx, Handl
                              "for more information. Note that only the first denied "
                              "property access from a given global object will be reported.",
                              NS_LossyConvertUTF16toASCII(propertyName).get(),
                              reason);
     } else {
         MOZ_ASSERT(type == WrapperDenialForCOW);
         errorMessage.emplace("Security wrapper denied access to property %s on privileged "
                              "Javascript object. Support for exposing privileged objects "
-                             "to untrusted content via __exposedProps__ is being gradually "
+                             "to untrusted content via __exposedProps__ has been "
                              "removed - use WebIDL bindings or Components.utils.cloneInto "
                              "instead. Note that only the first denied property access from a "
                              "given global object will be reported.",
                              NS_LossyConvertUTF16toASCII(propertyName).get());
     }
     nsString filenameStr(NS_ConvertASCIItoUTF16(filename.get()));
     nsresult rv = errorObject->InitWithWindowID(NS_ConvertASCIItoUTF16(errorMessage.ref()),
                                                 filenameStr,
--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -114,16 +114,29 @@ public:
       fragment = &mText.LastElement();
     }
 
     nsTArray<Glyph>& glyphs = fragment->glyphs;
 
     size_t oldLength = glyphs.Length();
     glyphs.SetLength(oldLength + aBuffer.mNumGlyphs);
     PodCopy(glyphs.Elements() + oldLength, aBuffer.mGlyphs, aBuffer.mNumGlyphs);
+
+    // If there's a skew for synthetic italics we need to apply it, as the font
+    // code applies the inverse transformation to glyph positions in anticipation.
+    Matrix trans = GetTransform();
+    if (trans._21 != 0) {
+      Matrix skew = Matrix(1, trans._12,
+                           trans._21, 1,
+                           0, 0);
+      for (size_t i = oldLength; i < oldLength + aBuffer.mNumGlyphs; ++i) {
+        auto position = &glyphs[i].mPosition;
+        *position = skew.TransformPoint(*position);
+      }
+    }
   }
 
   void AppendShadow(const wr::TextShadow& aShadow) { mShadows.AppendElement(aShadow); }
 
   void
   AppendSelection(const LayoutDeviceRect& aRect, const Color& aColor)
   {
     SelectionFragment frag;
--- a/layout/reftests/pixel-rounding/reftest.list
+++ b/layout/reftests/pixel-rounding/reftest.list
@@ -174,10 +174,10 @@ fuzzy-if(skiaContent,1,145) == rounded-b
 == background-image-tiling.html background-image-tiling-ref.html
 
 != border-image-width-0.html border-image-width-10.html
 == border-image-width-4.html border-image-width-0.html
 == border-image-width-9.html border-image-width-0.html
 
 fails-if(webrender) == iframe-1.html iframe-1-ref.html  # bug 1374378 for webrender
 
-fails-if(styloVsGecko||stylo) == viewport-units-rounding-1.html viewport-units-rounding-1-ref.html
+fuzzy-if(stylo||styloVsGecko,128,220) == viewport-units-rounding-1.html viewport-units-rounding-1-ref.html
 == viewport-units-rounding-2.html about:blank
rename from layout/reftests/svg/filters/filter-in-mask.svg
rename to layout/reftests/svg/filters/filter-in-mask-01.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/filters/filter-in-mask-02.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <defs>
+    <filter x="0" y="0" width="1" height="1" id="solid">
+      <feFlood flood-color="black"/>
+    </filter>
+    <mask id="myMask">
+      <rect fill="white" width="100%" height="100%"/>
+      <g transform="translate(10000, 0)">
+        <rect fill="blue" width="100%" height="100%" filter="url(#solid)"/>
+      </g>
+    </mask>
+  </defs>
+  <!-- You should see pure lime color as background color of a whole page. -->
+  <rect fill="red" width="100%" height="100%"/>
+  <g mask="url(#myMask)">
+    <rect x="0" y="0" width="100%" height="100%" fill="lime"></rect>
+  </g>
+</svg>
\ No newline at end of file
--- a/layout/reftests/svg/filters/reftest.list
+++ b/layout/reftests/svg/filters/reftest.list
@@ -63,17 +63,18 @@ skip-if(d2d) == feGaussianBlur-cap-large
 == feTile-1.svg feTile-1-ref.svg
 == feTile-2.svg feTile-2-ref.svg
 
 # no tests for feTurbulence
 
 == filter-clipped-rect-01.svg pass.svg
 == filter-in-pattern-01.svg pass.svg
 fuzzy(5,67) != filter-in-pattern-02.svg filter-in-pattern-02-ref.svg
-random-if(winWidget) == filter-in-mask.svg pass.svg # bug 1356139
+random-if(winWidget) == filter-in-mask-01.svg pass.svg # bug 1356139
+== filter-in-mask-02.svg pass.svg
 == filter-inner-svg-01.svg pass.svg
 == filter-inner-svg-02.svg pass.svg
 == filter-inner-svg-03.svg pass.svg
 fails == filter-marked-line-01.svg pass.svg # bug 477704
 == filter-kernelUnitLength-01.svg filter-kernelUnitLength-01-ref.svg
 == filter-marked-line-02.svg pass.svg
 == filter-marked-line-03.svg pass.svg
 == filter-marked-line-04.svg pass.svg
--- a/layout/style/ServoSpecifiedValues.cpp
+++ b/layout/style/ServoSpecifiedValues.cpp
@@ -23,27 +23,17 @@ ServoSpecifiedValues::ServoSpecifiedValu
 
   : GenericSpecifiedValues(StyleBackendType::Servo, aContext, ALL_SIDS)
   , mDecl(aDecl)
 {}
 
 bool
 ServoSpecifiedValues::PropertyIsSet(nsCSSPropertyID aId)
 {
-  // We always create fresh ServoSpecifiedValues for each property
-  // mapping, so unlike Gecko there aren't existing properties from
-  // the cascade that we wish to avoid overwriting.
-  //
-  // If a property is being overwritten, that's a bug. Check for it
-  // in debug mode (this is O(n^2) behavior since Servo will traverse
-  // the array each time you add a new property)
-  MOZ_ASSERT(!Servo_DeclarationBlock_PropertyIsSet(mDecl, aId),
-             "Presentation attribute mappers should never attempt to set the "
-             "same property twice");
-  return false;
+  return Servo_DeclarationBlock_PropertyIsSet(mDecl, aId);
 }
 
 void
 ServoSpecifiedValues::SetIdentStringValue(nsCSSPropertyID aId,
                                           const nsString& aValue)
 {
   nsCOMPtr<nsIAtom> atom = NS_Atomize(aValue);
   SetIdentAtomValue(aId, atom);
--- a/layout/style/ServoTypes.h
+++ b/layout/style/ServoTypes.h
@@ -153,16 +153,17 @@ enum ServoKeywordSize {
 // always guarantee that the interior representation
 // of this is correct (the mKeyword field may have a different padding),
 // but the entire struct should
 // have the same size and alignment as the Rust version.
 // Ensure layout tests get run if touching either side.
 struct ServoFontComputationData {
   ServoKeywordSize mKeyword;
   float/*32_t*/ mRatio;
+  int32_t mAbsolute;
 
   static_assert(sizeof(float) == 4, "float should be 32 bit");
 };
 
 struct ServoCustomPropertiesMap {
   uintptr_t mPtr;
 };
 
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1398479.html
@@ -0,0 +1,1 @@
+<table align="center" hspace="1">
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -211,9 +211,10 @@ load 1390726.html
 load 1393791.html
 load 1384232.html
 load 1395725.html
 load 1396041.html
 load 1397363-1.html
 load 1397439-1.html
 load 1395719.html
 load 1397091.html
+load 1398479.html
 load 1398581.html
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -169,16 +169,17 @@ skip-if = !stylo # This is a stylo test;
 [test_ch_ex_no_infloops.html]
 [test_change_hint_optimizations.html]
 [test_clip-path_polygon.html]
 [test_color_rounding.html]
 [test_compute_data_with_start_struct.html]
 skip-if = toolkit == 'android'
 [test_computed_style.html]
 [test_computed_style_bfcache_display_none.html]
+[test_computed_style_in_created_document.html]
 [test_computed_style_min_size_auto.html]
 [test_computed_style_no_pseudo.html]
 [test_computed_style_prefs.html]
 [test_condition_text.html]
 [test_condition_text_assignment.html]
 [test_contain_formatting_context.html]
 [test_counter_descriptor_storage.html]
 [test_counter_style.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_computed_style_in_created_document.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test for bug 1398619</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+let referenceFontSize = getComputedStyle(document.body).fontSize;
+
+function checkComputedStyle(desc, doc) {
+  try {
+    let fontSize = getComputedStyle(doc.body).fontSize;
+    is(fontSize, referenceFontSize, `${desc}: get computed font-size`);
+  } catch (e) {
+    ok(false, `${desc}: fail to get computed font-size, ${e}`);
+  }
+}
+
+async function runTest() {
+  // DOMParser
+  {
+    let parser = new DOMParser();
+    let doc = parser.parseFromString("<body>", "text/html");
+    checkComputedStyle("DOMParser", doc);
+  }
+  // DOMImplementation
+  {
+    let doc = document.implementation.createHTMLDocument("");
+    checkComputedStyle("DOMImplementation", doc);
+  }
+  // XMLHttpRequest
+  {
+    let xhr = new XMLHttpRequest();
+    xhr.open("GET", "empty.html");
+    xhr.responseType = "document";
+    let promise = new Promise(resolve => {
+      xhr.onload = resolve;
+    });
+    xhr.send();
+    await promise;
+    checkComputedStyle("XMLHttpRequest", xhr.responseXML);
+  }
+}
+runTest()
+  .catch(e => ok(false, `Exception: ${e}`))
+  .then(() => SimpleTest.finish());
+</script>
+</body>
+</html>
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -56,32 +56,48 @@ UserSpaceMetricsForFrame(nsIFrame* aFram
     nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent());
     return MakeUnique<SVGElementMetrics>(element);
   }
   return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
 }
 
 void
 nsFilterInstance::PaintFilteredFrame(nsIFrame *aFilteredFrame,
-                                     DrawTarget* aDrawTarget,
-                                     const gfxMatrix& aTransform,
+                                     gfxContext* aCtx,
                                      nsSVGFilterPaintCallback *aPaintCallback,
                                      const nsRegion *aDirtyArea,
                                      imgDrawingParams& aImgParams)
 {
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
+
+  gfxContextMatrixAutoSaveRestore autoSR(aCtx);
+  gfxSize scaleFactors = aCtx->CurrentMatrix().ScaleFactors(true);
+  gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
+                        0.0f, scaleFactors.height,
+                        0.0f, 0.0f);
+
+  gfxMatrix reverseScaleMatrix = scaleMatrix;
+  DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
+  MOZ_ASSERT(invertible);
+  // Pull scale vector out of aCtx's transform, put all scale factors, which
+  // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
+  aCtx->SetMatrix(reverseScaleMatrix * aCtx->CurrentMatrix());
+
+  gfxMatrix scaleMatrixInDevUnits =
+    scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
+
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
   nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
                             *metrics, filterChain, /* InputIsTainted */ true,
-                            aPaintCallback, aTransform, aDirtyArea, nullptr,
-                            nullptr, nullptr);
+                            aPaintCallback, scaleMatrixInDevUnits,
+                            aDirtyArea, nullptr, nullptr, nullptr);
   if (instance.IsInitialized()) {
-    instance.Render(aDrawTarget, aImgParams);
+    instance.Render(aCtx, aImgParams);
   }
 }
 
 nsRegion
 nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPreFilterDirtyRegion)
 {
   if (aPreFilterDirtyRegion.IsEmpty()) {
@@ -468,43 +484,41 @@ nsFilterInstance::BuildSourceImage(imgDr
 
   mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty, aImgParams);
 
   mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
   mSourceGraphic.mSurfaceRect = neededRect;
 }
 
 void
-nsFilterInstance::Render(DrawTarget* aDrawTarget, imgDrawingParams& aImgParams)
+nsFilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams)
 {
   MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");
 
   if (mPrimitiveDescriptions.IsEmpty()) {
     // An filter without any primitive. Treat it as success and paint nothing.
     return;
   }
 
   nsIntRect filterRect =
     mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
   if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
     return;
   }
 
-  AutoRestoreTransform autoRestoreTransform(aDrawTarget);
-  Matrix newTM =
-    aDrawTarget->GetTransform().PreTranslate(filterRect.x, filterRect.y);
-  aDrawTarget->SetTransform(newTM);
+  gfxContextMatrixAutoSaveRestore autoSR(aCtx);
+  aCtx->SetMatrix(aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
 
   ComputeNeededBoxes();
 
   BuildSourceImage(aImgParams);
   BuildSourcePaints(aImgParams);
 
   FilterSupport::RenderFilterDescription(
-    aDrawTarget, mFilterDescription, IntRectToRect(filterRect),
+    aCtx->GetDrawTarget(), mFilterDescription, IntRectToRect(filterRect),
     mSourceGraphic.mSourceSurface, mSourceGraphic.mSurfaceRect,
     mFillPaint.mSourceSurface, mFillPaint.mSurfaceRect,
     mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect,
     mInputImages, Point(0, 0));
 }
 
 nsRegion
 nsFilterInstance::ComputePostFilterDirtyRegion()
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -79,18 +79,17 @@ public:
 
   /**
    * Paint the given filtered frame.
    * @param aDirtyArea The area than needs to be painted, in aFilteredFrame's
    *   frame space (i.e. relative to its origin, the top-left corner of its
    *   border box).
    */
   static void PaintFilteredFrame(nsIFrame *aFilteredFrame,
-                                 DrawTarget* aDrawTarget,
-                                 const gfxMatrix& aTransform,
+                                 gfxContext* aCtx,
                                  nsSVGFilterPaintCallback *aPaintCallback,
                                  const nsRegion* aDirtyArea,
                                  imgDrawingParams& aImgParams);
 
   /**
    * Returns the post-filter area that could be dirtied when the given
    * pre-filter area of aFilteredFrame changes.
    * @param aPreFilterDirtyRegion The pre-filter area of aFilteredFrame that has
@@ -163,17 +162,17 @@ private:
   bool IsInitialized() const { return mInitialized; }
 
   /**
    * Draws the filter output into aDrawTarget. The area that
    * needs to be painted must have been specified before calling this method
    * by passing it as the aPostFilterDirtyRegion argument to the
    * nsFilterInstance constructor.
    */
-  void Render(DrawTarget* aDrawTarget, imgDrawingParams& aImgParams);
+  void Render(gfxContext* aCtx, imgDrawingParams& aImgParams);
 
   const FilterDescription& ExtractDescriptionAndAdditionalImages(nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
   {
     mInputImages.SwapElements(aOutAdditionalImages);
     return mFilterDescription;
   }
 
   /**
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -1089,30 +1089,19 @@ nsSVGIntegrationUtils::PaintFilter(const
     context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
                                   nullptr, Matrix());
   }
 
   /* Paint the child and apply filters */
   RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
                                      offsets.offsetToUserSpaceInDevPx);
   nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
-  gfxSize scaleFactors = context.CurrentMatrix().ScaleFactors(true);
-  gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
-                        0.0f, scaleFactors.height,
-                        0.0f, 0.0f);
-  gfxMatrix reverseScaleMatrix = scaleMatrix;
-  DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
-  MOZ_ASSERT(invertible);
-  context.SetMatrix(reverseScaleMatrix * context.CurrentMatrix());
 
-  gfxMatrix tm =
-    scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
-  nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
-                                       tm, &callback, &dirtyRegion,
-                                       aParams.imgParams);
+  nsFilterInstance::PaintFilteredFrame(frame, &context, &callback,
+                                       &dirtyRegion, aParams.imgParams);
 
   if (opacity != 1.0f) {
     context.PopGroupAndBlend();
   }
 }
 
 class PaintFrameCallback : public gfxDrawingCallback {
 public:
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -848,19 +848,29 @@ nsSVGUtils::PaintFrameWithEffects(nsIFra
                                       aDirtyRect->width, aDirtyRect->height));
       tmpDirtyRegion =
         nsLayoutUtils::RoundGfxRectToAppRect(
           dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) -
         aFrame->GetPosition();
       dirtyRegion = &tmpDirtyRegion;
     }
 
+    gfxContextMatrixAutoSaveRestore autoSR(target);
+
+    // 'target' is currently scaled such that its user space units are CSS
+    // pixels (SVG user space units). But PaintFilteredFrame expects it to be
+    // scaled in such a way that its user space units are device pixels. So we
+    // have to adjust the scale.
+    gfxMatrix reverseScaleMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(aFrame);
+    DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
+    target->SetMatrix(reverseScaleMatrix * aTransform *
+                      target->CurrentMatrix());
+
     SVGPaintCallback paintCallback;
-    nsFilterInstance::PaintFilteredFrame(aFrame, target->GetDrawTarget(),
-                                         aTransform, &paintCallback,
+    nsFilterInstance::PaintFilteredFrame(aFrame, target, &paintCallback,
                                          dirtyRegion, aImgParams);
   } else {
      svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect);
   }
 
   if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
     aContext.PopClip();
   }
--- a/layout/xul/nsSprocketLayout.cpp
+++ b/layout/xul/nsSprocketLayout.cpp
@@ -731,31 +731,27 @@ nsSprocketLayout::PopulateBoxSizes(nsIFr
     while (currentBox && currentBox->bogus) {
       last = currentBox;
       currentBox = currentBox->next;
     }
     ++childCount;
     nsSize pref(0,0);
     nsSize minSize(0,0);
     nsSize maxSize(NS_INTRINSICSIZE,NS_INTRINSICSIZE);
-    nscoord ascent = 0;
     bool collapsed = child->IsXULCollapsed();
 
     if (!collapsed) {
     // only one flexible child? Cool we will just make its preferred size
     // 0 then and not even have to ask for it.
     //if (flexes != 1)  {
 
       pref = child->GetXULPrefSize(aState);
       minSize = child->GetXULMinSize(aState);
       maxSize = nsBox::BoundsCheckMinMax(minSize, child->GetXULMaxSize(aState));
-      ascent = child->GetXULBoxAscent(aState);
-      nsMargin margin;
-      child->GetXULMargin(margin);
-      ascent += margin.top;
+      child->GetXULBoxAscent(aState);
     //}
 
       pref = nsBox::BoundsCheck(minSize, pref, maxSize);
 
       AddMargin(child, pref);
       AddMargin(child, minSize);
       AddMargin(child, maxSize);
     }
--- a/media/ffvpx/libavcodec/moz.build
+++ b/media/ffvpx/libavcodec/moz.build
@@ -51,17 +51,17 @@ SOURCES += [
     'vp9dsp.c',
     'vp9dsp_10bpp.c',
     'vp9dsp_12bpp.c',
     'vp9dsp_8bpp.c',
     'xiph.c'
 ]
 
 SYMBOLS_FILE = 'avcodec.symbols'
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 
 USE_LIBS += [
      'mozavutil'
 ]
 
 if CONFIG['OS_TARGET'] != 'WINNT':
     OS_LIBS += ['m']
 
--- a/media/ffvpx/libavutil/moz.build
+++ b/media/ffvpx/libavutil/moz.build
@@ -46,15 +46,15 @@ SOURCES += [
     'samplefmt.c',
     'threadmessage.c',
     'time.c',
     'timecode.c',
     'utils.c',
 ]
 
 SYMBOLS_FILE =  'avutil.symbols'
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 
 OS_LIBS += CONFIG['REALTIME_LIBS']
 if CONFIG['OS_TARGET'] != 'WINNT':
     OS_LIBS += ['m']
 
 include("../ffvpxcommon.mozbuild")
--- a/media/gmp-clearkey/0.1/moz.build
+++ b/media/gmp-clearkey/0.1/moz.build
@@ -47,17 +47,17 @@ if CONFIG['OS_ARCH'] == 'WINNT':
 
 
 DEFINES['CDM_IMPLEMENTATION'] = True
 
 TEST_DIRS += [
     'gtest',
 ]
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 DEFINES['MOZ_NO_MOZALLOC'] = True
 
 USE_LIBS += ['psshparser']
 
 # Suppress warnings in third-party code.
 if CONFIG['GNU_CXX']:
     CFLAGS += [
         '-Wno-missing-braces',
--- a/media/psshparser/moz.build
+++ b/media/psshparser/moz.build
@@ -12,14 +12,14 @@ EXPORTS.psshparser += [
 ]
 
 UNIFIED_SOURCES += [
     'PsshParser.cpp',
 ]
 
 Library('psshparser')
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 DEFINES['MOZ_NO_MOZALLOC'] = True
 
 TEST_DIRS += [
     'gtest',
 ]
--- a/media/webrtc/trunk/gtest/moz.build
+++ b/media/webrtc/trunk/gtest/moz.build
@@ -11,17 +11,17 @@
 ALLOW_COMPILER_WARNINGS = True
 
 DEFINES['GTEST_RELATIVE_PATH'] = True
 DEFINES['WEBRTC_APM_DEBUG_DUMP'] = True
 DEFINES['WEBRTC_INTELLIGIBILITY_ENHANCER'] = 0
 DEFINES['WEBRTC_MOZILLA_BUILD'] = 1
 
 # Hit build errors on windows with xutility otherwise
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 LOCAL_INCLUDES += [
     '../',
     '/',
     '/ipc/chromium/src/',
     '/media/libopus/celt/',
     '/media/libopus/include',
     '/media/libopus/src',
rename from memory/mozjemalloc/Makefile.in
rename to memory/build/Makefile.in
--- a/memory/build/moz.build
+++ b/memory/build/moz.build
@@ -1,30 +1,32 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS += [
+    'mozjemalloc_types.h',
     'mozmemory.h',
     'mozmemory_wrap.h',
 ]
 
 LIBRARY_DEFINES['MOZ_HAS_MOZGLUE'] = True
 DEFINES['MOZ_MEMORY_IMPL'] = True
 
 if CONFIG['MOZ_REPLACE_MALLOC']:
     EXPORTS += [
         'malloc_decls.h',
         'replace_malloc.h',
         'replace_malloc_bridge.h',
     ]
 
 SOURCES += [
+    'mozjemalloc.cpp',
     'mozmemory_wrap.c',
 ]
 
 if CONFIG['OS_TARGET'] == 'Darwin' and (CONFIG['MOZ_REPLACE_MALLOC'] or
         CONFIG['MOZ_MEMORY']):
     SOURCES += [
         'zone.c',
     ]
@@ -37,8 +39,18 @@ if CONFIG['MOZ_GLUE_IN_PROGRAM']:
 if CONFIG['OS_TARGET'] == 'Android' and CONFIG['CC_TYPE'] == 'clang':
     CFLAGS += [
         '-Wno-tautological-pointer-compare',
     ]
 
 # Keep jemalloc separated when mozglue is statically linked
 if CONFIG['MOZ_MEMORY'] and CONFIG['OS_TARGET'] in ('WINNT', 'Darwin', 'Android'):
     FINAL_LIBRARY = 'mozglue'
+
+if CONFIG['GNU_CXX']:
+    # too many warnings from functions generated through rb_wrab from rb.h.
+    CXXFLAGS += ['-Wno-unused-function',
+                 '-Wno-error=uninitialized']
+
+if CONFIG['_MSC_VER']:
+    CXXFLAGS += ['-wd4273'] # inconsistent dll linkage (bug 558163)
+
+DisableStlWrapping()
rename from memory/mozjemalloc/mozjemalloc.cpp
rename to memory/build/mozjemalloc.cpp
--- a/memory/mozjemalloc/mozjemalloc.cpp
+++ b/memory/build/mozjemalloc.cpp
@@ -107,16 +107,17 @@
  *******************************************************************************
  */
 
 #include "mozmemory_wrap.h"
 #include "mozjemalloc.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MacroArgs.h"
+#include "mozilla/DoublyLinkedList.h"
 
 #ifdef ANDROID
 #define NO_TLS
 #endif
 
 /*
  * On Linux, we use madvise(MADV_DONTNEED) to release memory back to the
  * operating system.  If we release 1MB of live pages with MADV_DONTNEED, our
@@ -247,17 +248,16 @@ typedef long ssize_t;
 #include <mach/mach_init.h>
 #include <mach/vm_map.h>
 #include <malloc/malloc.h>
 #endif
 
 #endif
 
 #include "mozjemalloc_types.h"
-#include "linkedlist.h"
 
 /* Some tools, such as /dev/dsp wrappers, LD_PRELOAD libraries that
  * happen to override mmap() and call dlsym() from their overridden
  * mmap(). The problem is that dlsym() calls malloc(), and this ends
  * up in a dead lock in jemalloc.
  * On these systems, we prefer to directly use the system call.
  * We do that for Linux systems and kfreebsd with GNU userland.
  * Note sanity checks are not done (alignment of offset, ...) because
@@ -622,27 +622,42 @@ struct arena_chunk_t {
 
 #ifdef MALLOC_DOUBLE_PURGE
 	/* If we're double-purging, we maintain a linked list of chunks which
 	 * have pages which have been madvise(MADV_FREE)'d but not explicitly
 	 * purged.
 	 *
 	 * We're currently lazy and don't remove a chunk from this list when
 	 * all its madvised pages are recommitted. */
-	LinkedList	chunks_madvised_elem;
+	mozilla::DoublyLinkedListElement<arena_chunk_t> chunks_madvised_elem;
 #endif
 
 	/* Number of dirty pages. */
 	size_t		ndirty;
 
 	/* Map of pages within chunk that keeps track of free/large/small. */
 	arena_chunk_map_t map[1]; /* Dynamically sized. */
 };
 typedef rb_tree(arena_chunk_t) arena_chunk_tree_t;
 
+#ifdef MALLOC_DOUBLE_PURGE
+namespace mozilla {
+
+template<>
+struct GetDoublyLinkedListElement<arena_chunk_t>
+{
+  static DoublyLinkedListElement<arena_chunk_t>& Get(arena_chunk_t* aThis)
+  {
+    return aThis->chunks_madvised_elem;
+  }
+};
+
+}
+#endif
+
 struct arena_run_t {
 #if defined(MOZ_DEBUG) || defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
 	uint32_t	magic;
 #  define ARENA_RUN_MAGIC 0x384adf93
 #endif
 
 	/* Bin this run is associated with. */
 	arena_bin_t	*bin;
@@ -704,17 +719,17 @@ struct arena_t {
 	arena_stats_t		stats;
 
 	/* Tree of dirty-page-containing chunks this arena manages. */
 	arena_chunk_tree_t	chunks_dirty;
 
 #ifdef MALLOC_DOUBLE_PURGE
 	/* Head of a linked list of MADV_FREE'd-page-containing chunks this
 	 * arena manages. */
-	LinkedList		chunks_madvised;
+	mozilla::DoublyLinkedList<arena_chunk_t> chunks_madvised;
 #endif
 
 	/*
 	 * In order to avoid rapid chunk allocation/deallocation when an arena
 	 * oscillates right on the cusp of needing a new chunk, cache the most
 	 * recently freed chunk.  The spare is left in the arena's chunk trees
 	 * until it is deleted.
 	 *
@@ -2694,35 +2709,36 @@ arena_chunk_init(arena_t *arena, arena_c
 #endif
 	arena->stats.committed += arena_chunk_header_npages;
 
 	/* Insert the run into the runs_avail tree. */
 	arena_avail_tree_insert(&arena->runs_avail,
 	    &chunk->map[arena_chunk_header_npages]);
 
 #ifdef MALLOC_DOUBLE_PURGE
-	LinkedList_Init(&chunk->chunks_madvised_elem);
+	new (&chunk->chunks_madvised_elem) mozilla::DoublyLinkedListElement<arena_chunk_t>();
 #endif
 }
 
 static void
 arena_chunk_dealloc(arena_t *arena, arena_chunk_t *chunk)
 {
 
 	if (arena->spare) {
 		if (arena->spare->ndirty > 0) {
 			arena_chunk_tree_dirty_remove(
 			    &chunk->arena->chunks_dirty, arena->spare);
 			arena->ndirty -= arena->spare->ndirty;
 			arena->stats.committed -= arena->spare->ndirty;
 		}
 
 #ifdef MALLOC_DOUBLE_PURGE
-		/* This is safe to do even if arena->spare is not in the list. */
-		LinkedList_Remove(&arena->spare->chunks_madvised_elem);
+		if (arena->chunks_madvised.ElementProbablyInList(arena->spare)) {
+			arena->chunks_madvised.remove(arena->spare);
+		}
 #endif
 
 		chunk_dealloc((void *)arena->spare, chunksize, ARENA_CHUNK);
 		arena->stats.mapped -= chunksize;
 		arena->stats.committed -= arena_chunk_header_npages;
 	}
 
 	/*
@@ -2873,18 +2889,20 @@ arena_purge(arena_t *arena, bool all)
 		if (chunk->ndirty == 0) {
 			arena_chunk_tree_dirty_remove(&arena->chunks_dirty,
 			    chunk);
 		}
 #ifdef MALLOC_DOUBLE_PURGE
 		if (madvised) {
 			/* The chunk might already be in the list, but this
 			 * makes sure it's at the front. */
-			LinkedList_Remove(&chunk->chunks_madvised_elem);
-			LinkedList_InsertHead(&arena->chunks_madvised, &chunk->chunks_madvised_elem);
+			if (arena->chunks_madvised.ElementProbablyInList(chunk)) {
+				arena->chunks_madvised.remove(chunk);
+			}
+			arena->chunks_madvised.pushFront(chunk);
 		}
 #endif
 	}
 }
 
 static void
 arena_run_dalloc(arena_t *arena, arena_run_t *run, bool dirty)
 {
@@ -4018,17 +4036,17 @@ arena_new(arena_t *arena)
 	if (malloc_spin_init(&arena->lock))
 		return (true);
 
 	memset(&arena->stats, 0, sizeof(arena_stats_t));
 
 	/* Initialize chunks. */
 	arena_chunk_tree_dirty_new(&arena->chunks_dirty);
 #ifdef MALLOC_DOUBLE_PURGE
-	LinkedList_Init(&arena->chunks_madvised);
+	new (&arena->chunks_madvised) mozilla::DoublyLinkedList<arena_chunk_t>();
 #endif
 	arena->spare = nullptr;
 
 	arena->ndirty = 0;
 
 	arena_avail_tree_new(&arena->runs_avail);
 
 	/* Initialize bins. */
@@ -5073,22 +5091,19 @@ hard_purge_chunk(arena_chunk_t *chunk)
 }
 
 /* Explicitly remove all of this arena's MADV_FREE'd pages from memory. */
 static void
 hard_purge_arena(arena_t *arena)
 {
 	malloc_spin_lock(&arena->lock);
 
-	while (!LinkedList_IsEmpty(&arena->chunks_madvised)) {
-		arena_chunk_t *chunk =
-			LinkedList_Get(arena->chunks_madvised.next,
-				       arena_chunk_t, chunks_madvised_elem);
+	while (!arena->chunks_madvised.isEmpty()) {
+		arena_chunk_t *chunk = arena->chunks_madvised.popFront();
 		hard_purge_chunk(chunk);
-		LinkedList_Remove(&chunk->chunks_madvised_elem);
 	}
 
 	malloc_spin_unlock(&arena->lock);
 }
 
 template<> inline void
 MozJemalloc::jemalloc_purge_freed_pages()
 {
rename from memory/mozjemalloc/mozjemalloc.h
rename to memory/build/mozjemalloc.h
rename from memory/mozjemalloc/mozjemalloc_types.h
rename to memory/build/mozjemalloc_types.h
--- a/memory/build/mozmemory_wrap.h
+++ b/memory/build/mozmemory_wrap.h
@@ -87,19 +87,16 @@
  *   char* strdup_impl(const char *)
  * That implementation would call malloc by using "malloc_impl".
  */
 
 #ifndef MOZ_MEMORY
 #  error Should only include mozmemory_wrap.h when MOZ_MEMORY is set.
 #endif
 
-#if defined(MOZ_JEMALLOC_IMPL) && !defined(MOZ_MEMORY_IMPL)
-#  define MOZ_MEMORY_IMPL
-#endif
 #if defined(MOZ_MEMORY_IMPL) && !defined(IMPL_MFBT)
 #  ifdef MFBT_API /* mozilla/Types.h was already included */
 #    error mozmemory_wrap.h has to be included before mozilla/Types.h when MOZ_MEMORY_IMPL is set and IMPL_MFBT is not.
 #  endif
 #  define IMPL_MFBT
 #endif
 
 #include "mozilla/Types.h"
rename from memory/mozjemalloc/rb.h
rename to memory/build/rb.h
--- a/memory/moz.build
+++ b/memory/moz.build
@@ -15,13 +15,12 @@ DIRS += [
 if not CONFIG['JS_STANDALONE']:
     DIRS += ['volatile']
 
 if CONFIG['MOZ_MEMORY']:
     # NB: gtest dir is included in toolkit/toolkit.build due to its dependency
     # on libxul.
     DIRS += [
         'build',
-        'mozjemalloc',
     ]
 
     if CONFIG['MOZ_REPLACE_MALLOC']:
         DIRS += ['replace']
--- a/memory/mozalloc/moz.build
+++ b/memory/mozalloc/moz.build
@@ -1,14 +1,14 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 
 EXPORTS.mozilla += [
     'mozalloc.h',
     'mozalloc_abort.h',
     'mozalloc_oom.h',
 ]
 
 if CONFIG['WRAP_STL_INCLUDES']:
@@ -33,17 +33,17 @@ UNIFIED_SOURCES += [
     'mozalloc_oom.cpp',
 ]
 
 FINAL_LIBRARY = 'mozglue'
 
 # The strndup declaration in string.h is in an ifdef __USE_GNU section
 DEFINES['_GNU_SOURCE'] = True
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 DEFINES['IMPL_MFBT'] = True
 
 if CONFIG['_MSC_VER']:
     DIRS += ['staticruntime']
 
 LOCAL_INCLUDES += [
     '!/xpcom',
--- a/memory/mozalloc/staticruntime/moz.build
+++ b/memory/mozalloc/staticruntime/moz.build
@@ -1,15 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-NO_VISIBILITY_FLAGS = True
+NoVisibilityFlags()
 
 if CONFIG['WRAP_STL_INCLUDES']:
     DEFINES['_HAS_EXCEPTIONS'] = 0
     if CONFIG['MOZ_MSVC_STL_WRAP_RAISE']:
         SOURCES += [
             '../msvc_raise_wrappers.cpp',
         ]
 
@@ -21,15 +21,15 @@ UNIFIED_SOURCES += [
 
 # Keep this file separate to avoid #include'ing windows.h everywhere.
 SOURCES += [
     '../winheap.cpp',
 ]
 
 LOCAL_INCLUDES += ['!/xpcom']
 
-DISABLE_STL_WRAPPING = True
+DisableStlWrapping()
 
 DEFINES['IMPL_MFBT'] = True
 
 USE_STATIC_LIBS = True
 
 Library('mozalloc_staticruntime')
deleted file mode 100644
--- a/memory/mozjemalloc/linkedlist.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/* -*- Mode: C; tab-width: 8; c-basic-offset: 8; indent-tabs-mode: t -*- */
-/* vim:set softtabstop=8 shiftwidth=8 noet: */
-/*-
- * Copyright (C) the Mozilla Foundation.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice(s), this list of conditions and the following disclaimer as
- *    the first lines of this file unmodified other than the possible
- *    addition of one or more copyright notices.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice(s), this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
- * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- *******************************************************************************/
-
-#ifndef linkedlist_h__
-#define linkedlist_h__
-
-#include <stddef.h>
-
-struct LinkedList {
-	LinkedList *next;
-	LinkedList *prev;
-};
-
-/* Convert from LinkedList* to foo*. */
-#define LinkedList_Get(e, type, prop) \
-  (type*)((char*)(e) - offsetof(type, prop))
-
-/* Insert |e| at the beginning of |l|.  */
-void LinkedList_InsertHead(LinkedList *l, LinkedList *e)
-{
-	e->next = l;