Bug 1430272 - Add preloaded updating, whitelisted regions and bug fixes to Activity Stream. r?dmose draft
authorEd Lee <edilee@mozilla.com>
Fri, 12 Jan 2018 14:12:57 -0800
changeset 719929 05e2c5aae4546a70be071fb0fcf96c07420184d4
parent 719928 f5b4481c9fd50becb35cef02b599198b766fb1bb
child 745926 5c520c1eef32d4fba03c4feef68062be302925d3
push id95394
push userbmo:edilee@mozilla.com
push dateFri, 12 Jan 2018 22:16:07 +0000
reviewersdmose
bugs1430272
milestone59.0a1
Bug 1430272 - Add preloaded updating, whitelisted regions and bug fixes to Activity Stream. r?dmose MozReview-Commit-ID: 6wWmVoXCQO5
browser/components/newtab/aboutNewTabService.js
browser/components/newtab/tests/browser/browser.ini
browser/extensions/activity-stream/common/Actions.jsm
browser/extensions/activity-stream/data/content/activity-stream.bundle.js
browser/extensions/activity-stream/install.rdf.in
browser/extensions/activity-stream/lib/ActivityStream.jsm
browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm
browser/extensions/activity-stream/lib/PlacesFeed.jsm
browser/extensions/activity-stream/lib/SectionsManager.jsm
browser/extensions/activity-stream/lib/TelemetryFeed.jsm
browser/extensions/activity-stream/lib/TopSitesFeed.jsm
browser/extensions/activity-stream/prerendered/locales/dsb/activity-stream-strings.js
browser/extensions/activity-stream/prerendered/locales/hsb/activity-stream-strings.js
browser/extensions/activity-stream/prerendered/locales/pt-BR/activity-stream-strings.js
browser/extensions/activity-stream/prerendered/locales/ro/activity-stream-strings.js
browser/extensions/activity-stream/prerendered/locales/te/activity-stream-strings.js
browser/extensions/activity-stream/test/.eslintrc.js
browser/extensions/activity-stream/test/functional/mochitest/browser.ini
browser/extensions/activity-stream/test/functional/mochitest/browser_highlights_section.js
browser/extensions/activity-stream/test/functional/mochitest/browser_topsites_section.js
browser/extensions/activity-stream/test/functional/mochitest/head.js
browser/extensions/activity-stream/test/unit/common/Actions.test.js
browser/extensions/activity-stream/test/unit/lib/ActivityStream.test.js
browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js
browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
browser/extensions/activity-stream/test/unit/lib/UserDomainAffinityProvider.test.js
browser/extensions/activity-stream/test/unit/unit-entry.js
browser/modules/PingCentre.jsm
--- a/browser/components/newtab/aboutNewTabService.js
+++ b/browser/components/newtab/aboutNewTabService.js
@@ -16,17 +16,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/AboutNewTab.jsm");
 
 const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
 const TOPIC_APP_QUIT = "quit-application-granted";
 const TOPIC_LOCALES_CHANGE = "intl:requested-locales-changed";
 
 // Automated tests ensure packaged locales are in this list. Copied output of:
 // https://github.com/mozilla/activity-stream/blob/master/bin/render-activity-stream-html.js
-const ACTIVITY_STREAM_LOCALES = new Set("en-US ach ar ast az be bg bn-BD bn-IN br bs ca cak cs cy da de dsb el en-GB eo es-AR es-CL es-ES es-MX et eu fa ff fi fr fy-NL ga-IE gd gl gn gu-IN he hi-IN hr hsb hu hy-AM ia id it ja ka kab kk km kn ko lij lo lt ltg lv mk ml mr ms my nb-NO ne-NP nl nn-NO pa-IN pl pt-BR pt-PT rm ro ru si sk sl sq sr sv-SE ta te th tl tr uk ur uz vi zh-CN zh-TW".split(" "));
+const ACTIVITY_STREAM_LOCALES = "en-US ach ar ast az be bg bn-BD bn-IN br bs ca cak cs cy da de dsb el en-GB eo es-AR es-CL es-ES es-MX et eu fa ff fi fr fy-NL ga-IE gd gl gn gu-IN he hi-IN hr hsb hu hy-AM ia id it ja ka kab kk km kn ko lij lo lt ltg lv mk ml mr ms my nb-NO ne-NP nl nn-NO pa-IN pl pt-BR pt-PT rm ro ru si sk sl sq sr sv-SE ta te th tl tr uk ur uz vi zh-CN zh-TW".split(" ");
 
 const ABOUT_URL = "about:newtab";
 
 const IS_MAIN_PROCESS = Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
 
 const IS_RELEASE_OR_BETA = AppConstants.RELEASE_OR_BETA;
 
 // Pref that tells if activity stream is enabled
@@ -174,36 +174,24 @@ AboutNewTabService.prototype = {
     return true;
   },
 
   /**
    * Figure out what path under prerendered to use based on current state.
    */
   updatePrerenderedPath() {
     // Debug files are specially packaged in a non-localized directory
-    let path;
-    if (this._activityStreamDebug) {
-      path = "static";
-    } else {
-      // Use the exact match locale if it's packaged
-      const locale = Services.locale.getRequestedLocale();
-      if (ACTIVITY_STREAM_LOCALES.has(locale)) {
-        path = locale;
-      } else {
-        // Fall back to a shared-language packaged locale
-        const language = locale.split("-")[0];
-        if (ACTIVITY_STREAM_LOCALES.has(language)) {
-          path = language;
-        } else {
-          // Just use the default locale as a final fallback
-          path = "en-US";
-        }
-      }
-    }
-    this._activityStreamPath = `${path}/`;
+    this._activityStreamPath = `${this._activityStreamDebug ? "static" :
+      // Pick the best available locale to match the app locales
+      Services.locale.negotiateLanguages(
+        Services.locale.getAppLocalesAsLangTags(),
+        ACTIVITY_STREAM_LOCALES,
+        // defaultLocale's strings aren't necessarily packaged, but en-US' are
+        "en-US"
+      )[0]}/`;
   },
 
   /*
    * Returns the default URL.
    *
    * This URL only depends on the browser.newtabpage.activity-stream.enabled pref. Overriding
    * the newtab page has no effect on the result of this function.
    *
--- a/browser/components/newtab/tests/browser/browser.ini
+++ b/browser/components/newtab/tests/browser/browser.ini
@@ -1,4 +1,5 @@
 [DEFAULT]
 
 [browser_packaged_as_locales.js]
+skip-if=true # bug 1423703 comment 20
 [browser_newtab_overrides.js]
--- a/browser/extensions/activity-stream/common/Actions.jsm
+++ b/browser/extensions/activity-stream/common/Actions.jsm
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 this.MAIN_MESSAGE_TYPE = "ActivityStream:Main";
 this.CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
+this.PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser";
 this.UI_CODE = 1;
 this.BACKGROUND_PROCESS = 2;
 
 /**
  * globalImportContext - Are we in UI code (i.e. react, a dom) or some kind of background process?
  *                       Use this in action creators if you need different logic
  *                       for ui/background processes.
  */
@@ -151,16 +152,29 @@ function SendToContent(action, target) {
   return _RouteMessage(action, {
     from: MAIN_MESSAGE_TYPE,
     to: CONTENT_MESSAGE_TYPE,
     toTarget: target
   });
 }
 
 /**
+ * SendToPreloaded - Creates a message that will be sent to the preloaded tab.
+ *
+ * @param  {object} action Any redux action (required)
+ * @return {object} An action with added .meta properties
+ */
+function SendToPreloaded(action) {
+  return _RouteMessage(action, {
+    from: MAIN_MESSAGE_TYPE,
+    to: PRELOAD_MESSAGE_TYPE
+  });
+}
+
+/**
  * UserEvent - A telemetry ping indicating a user action. This should only
  *                   be sent from the UI during a user session.
  *
  * @param  {object} data Fields to include in the ping (source, etc.)
  * @return {object} An SendToMain action
  */
 function UserEvent(data) {
   return SendToMain({
@@ -224,16 +238,17 @@ this.actionTypes = actionTypes;
 this.actionCreators = {
   BroadcastToContent,
   UserEvent,
   UndesiredEvent,
   PerfEvent,
   ImpressionStats,
   SendToContent,
   SendToMain,
+  SendToPreloaded,
   SetPref
 };
 
 // These are helpers to test for certain kinds of actions
 this.actionUtils = {
   isSendToMain(action) {
     if (!action.meta) {
       return false;
@@ -253,16 +268,23 @@ this.actionUtils = {
     if (!action.meta) {
       return false;
     }
     if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
       return true;
     }
     return false;
   },
+  isSendToPreloaded(action) {
+    if (!action.meta) {
+      return false;
+    }
+    return action.meta.to === PRELOAD_MESSAGE_TYPE &&
+      action.meta.from === MAIN_MESSAGE_TYPE;
+  },
   isFromMain(action) {
     if (!action.meta) {
       return false;
     }
     return action.meta.from === MAIN_MESSAGE_TYPE &&
       action.meta.to === CONTENT_MESSAGE_TYPE;
   },
   getPortIdOfSender(action) {
@@ -274,10 +296,11 @@ this.actionUtils = {
 this.EXPORTED_SYMBOLS = [
   "actionTypes",
   "actionCreators",
   "actionUtils",
   "globalImportContext",
   "UI_CODE",
   "BACKGROUND_PROCESS",
   "MAIN_MESSAGE_TYPE",
-  "CONTENT_MESSAGE_TYPE"
+  "CONTENT_MESSAGE_TYPE",
+  "PRELOAD_MESSAGE_TYPE"
 ];
--- a/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
+++ b/browser/extensions/activity-stream/data/content/activity-stream.bundle.js
@@ -60,45 +60,57 @@
 /******/ 	__webpack_require__.p = "";
 /******/
 /******/ 	// Load entry module and return exports
 /******/ 	return __webpack_require__(__webpack_require__.s = 10);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
+/* unused harmony export MAIN_MESSAGE_TYPE */
+/* unused harmony export CONTENT_MESSAGE_TYPE */
+/* unused harmony export PRELOAD_MESSAGE_TYPE */
+/* unused harmony export UI_CODE */
+/* unused harmony export BACKGROUND_PROCESS */
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return actionCreators; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return actionUtils; });
 /* 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 MAIN_MESSAGE_TYPE = "ActivityStream:Main";
 var CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
+var PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser";
 var UI_CODE = 1;
 var BACKGROUND_PROCESS = 2;
 
 /**
  * globalImportContext - Are we in UI code (i.e. react, a dom) or some kind of background process?
  *                       Use this in action creators if you need different logic
  *                       for ui/background processes.
  */
 
 const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS : UI_CODE;
+/* unused harmony export globalImportContext */
+
 // Export for tests
 
-
 // Create an object that avoids accidental differing key/value pairs:
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 const actionTypes = {};
+/* harmony export (immutable) */ __webpack_exports__["b"] = actionTypes;
+
+
 for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_ONBOARDING", "INIT", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "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) : {};
@@ -160,16 +172,29 @@ function SendToContent(action, target) {
   return _RouteMessage(action, {
     from: MAIN_MESSAGE_TYPE,
     to: CONTENT_MESSAGE_TYPE,
     toTarget: target
   });
 }
 
 /**
+ * SendToPreloaded - Creates a message that will be sent to the preloaded tab.
+ *
+ * @param  {object} action Any redux action (required)
+ * @return {object} An action with added .meta properties
+ */
+function SendToPreloaded(action) {
+  return _RouteMessage(action, {
+    from: MAIN_MESSAGE_TYPE,
+    to: PRELOAD_MESSAGE_TYPE
+  });
+}
+
+/**
  * UserEvent - A telemetry ping indicating a user action. This should only
  *                   be sent from the UI during a user session.
  *
  * @param  {object} data Fields to include in the ping (source, etc.)
  * @return {object} An SendToMain action
  */
 function UserEvent(data) {
   return SendToMain({
@@ -231,16 +256,17 @@ function SetPref(name, value, importCont
 var actionCreators = {
   BroadcastToContent,
   UserEvent,
   UndesiredEvent,
   PerfEvent,
   ImpressionStats,
   SendToContent,
   SendToMain,
+  SendToPreloaded,
   SetPref
 };
 
 // These are helpers to test for certain kinds of actions
 
 var actionUtils = {
   isSendToMain(action) {
     if (!action.meta) {
@@ -261,37 +287,33 @@ var actionUtils = {
     if (!action.meta) {
       return false;
     }
     if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
       return true;
     }
     return false;
   },
+  isSendToPreloaded(action) {
+    if (!action.meta) {
+      return false;
+    }
+    return action.meta.to === PRELOAD_MESSAGE_TYPE && action.meta.from === MAIN_MESSAGE_TYPE;
+  },
   isFromMain(action) {
     if (!action.meta) {
       return false;
     }
     return action.meta.from === MAIN_MESSAGE_TYPE && action.meta.to === CONTENT_MESSAGE_TYPE;
   },
   getPortIdOfSender(action) {
     return action.meta && action.meta.fromTarget || null;
   },
   _RouteMessage
 };
-module.exports = {
-  actionTypes,
-  actionCreators,
-  actionUtils,
-  globalImportContext,
-  UI_CODE,
-  BACKGROUND_PROCESS,
-  MAIN_MESSAGE_TYPE,
-  CONTENT_MESSAGE_TYPE
-};
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports) {
 
 module.exports = React;
 
 /***/ }),
@@ -330,29 +352,73 @@ try {
 // We return undefined, instead of nothing here, so it's
 // easier to handle this case. if(!global) { ...}
 
 module.exports = g;
 
 
 /***/ }),
 /* 5 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
+
+// EXTERNAL MODULE: ./system-addon/common/Actions.jsm
+var Actions = __webpack_require__(0);
+
+// CONCATENATED MODULE: ./system-addon/common/Dedupe.jsm
+class Dedupe {
+  constructor(createKey) {
+    this.createKey = createKey || this.defaultCreateKey;
+  }
+
+  defaultCreateKey(item) {
+    return item;
+  }
+
+  /**
+   * Dedupe any number of grouped elements favoring those from earlier groups.
+   *
+   * @param {Array} groups Contains an arbitrary number of arrays of elements.
+   * @returns {Array} A matching array of each provided group deduped.
+   */
+  group(...groups) {
+    const globalKeys = new Set();
+    const result = [];
+    for (const values of groups) {
+      const valueMap = new Map();
+      for (const value of values) {
+        const key = this.createKey(value);
+        if (!globalKeys.has(key) && !valueMap.has(key)) {
+          valueMap.set(key, value);
+        }
+      }
+      result.push(valueMap);
+      valueMap.forEach((value, key) => globalKeys.add(key));
+    }
+    return result.map(m => Array.from(m.values()));
+  }
+}
+// CONCATENATED MODULE: ./system-addon/common/Reducers.jsm
+/* unused harmony export insertPinned */
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return reducers; });
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
-const { actionTypes: at } = __webpack_require__(0);
-const { Dedupe } = __webpack_require__(13);
+
 
 const TOP_SITES_DEFAULT_LENGTH = 6;
+/* harmony export (immutable) */ __webpack_exports__["a"] = TOP_SITES_DEFAULT_LENGTH;
+
 const TOP_SITES_SHOWMORE_LENGTH = 12;
+/* harmony export (immutable) */ __webpack_exports__["b"] = TOP_SITES_SHOWMORE_LENGTH;
+
+
 
 const dedupe = new Dedupe(site => site && site.url);
 
 const INITIAL_STATE = {
   App: {
     // Have we received real data from the app yet?
     initialized: false,
     // The version of the system-addon
@@ -377,20 +443,23 @@ const INITIAL_STATE = {
   },
   Dialog: {
     visible: false,
     data: {}
   },
   Sections: [],
   PreferencesPane: { visible: false }
 };
+/* unused harmony export INITIAL_STATE */
+
+
 
 function App(prevState = INITIAL_STATE.App, action) {
   switch (action.type) {
-    case at.INIT:
+    case Actions["b" /* actionTypes */].INIT:
       return Object.assign({}, prevState, action.data || {}, { initialized: true });
     default:
       return prevState;
   }
 }
 
 /**
  * insertPinned - Inserts pinned links in their specified slots
@@ -422,51 +491,52 @@ function insertPinned(links, pinned) {
     } else {
       newLinks.splice(index, 0, link);
     }
   });
 
   return newLinks;
 }
 
+
 function TopSites(prevState = INITIAL_STATE.TopSites, action) {
   let hasMatch;
   let newRows;
   switch (action.type) {
-    case at.TOP_SITES_UPDATED:
+    case Actions["b" /* actionTypes */].TOP_SITES_UPDATED:
       if (!action.data) {
         return prevState;
       }
       return Object.assign({}, prevState, { initialized: true, rows: action.data });
-    case at.TOP_SITES_EDIT:
+    case Actions["b" /* actionTypes */].TOP_SITES_EDIT:
       return Object.assign({}, prevState, { editForm: { visible: true, index: action.data.index } });
-    case at.TOP_SITES_CANCEL_EDIT:
+    case Actions["b" /* actionTypes */].TOP_SITES_CANCEL_EDIT:
       return Object.assign({}, prevState, { editForm: { visible: false } });
-    case at.SCREENSHOT_UPDATED:
+    case Actions["b" /* actionTypes */].SCREENSHOT_UPDATED:
       newRows = prevState.rows.map(row => {
         if (row && row.url === action.data.url) {
           hasMatch = true;
           return Object.assign({}, row, { screenshot: action.data.screenshot });
         }
         return row;
       });
       return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState;
-    case at.PLACES_BOOKMARK_ADDED:
+    case Actions["b" /* actionTypes */].PLACES_BOOKMARK_ADDED:
       if (!action.data) {
         return prevState;
       }
       newRows = prevState.rows.map(site => {
         if (site && site.url === action.data.url) {
           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:
+    case Actions["b" /* actionTypes */].PLACES_BOOKMARK_REMOVED:
       if (!action.data) {
         return prevState;
       }
       newRows = prevState.rows.map(site => {
         if (site && site.url === action.data.url) {
           const newSite = Object.assign({}, site);
           delete newSite.bookmarkGuid;
           delete newSite.bookmarkTitle;
@@ -478,48 +548,48 @@ function TopSites(prevState = INITIAL_ST
       return Object.assign({}, prevState, { rows: newRows });
     default:
       return prevState;
   }
 }
 
 function Dialog(prevState = INITIAL_STATE.Dialog, action) {
   switch (action.type) {
-    case at.DIALOG_OPEN:
+    case Actions["b" /* actionTypes */].DIALOG_OPEN:
       return Object.assign({}, prevState, { visible: true, data: action.data });
-    case at.DIALOG_CANCEL:
+    case Actions["b" /* actionTypes */].DIALOG_CANCEL:
       return Object.assign({}, prevState, { visible: false });
-    case at.DELETE_HISTORY_URL:
+    case Actions["b" /* actionTypes */].DELETE_HISTORY_URL:
       return Object.assign({}, INITIAL_STATE.Dialog);
     default:
       return prevState;
   }
 }
 
 function Prefs(prevState = INITIAL_STATE.Prefs, action) {
   let newValues;
   switch (action.type) {
-    case at.PREFS_INITIAL_VALUES:
+    case Actions["b" /* actionTypes */].PREFS_INITIAL_VALUES:
       return Object.assign({}, prevState, { initialized: true, values: action.data });
-    case at.PREF_CHANGED:
+    case Actions["b" /* actionTypes */].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(prevState = INITIAL_STATE.Sections, action) {
   let hasMatch;
   let newState;
   switch (action.type) {
-    case at.SECTION_DEREGISTER:
+    case Actions["b" /* actionTypes */].SECTION_DEREGISTER:
       return prevState.filter(section => section.id !== action.data);
-    case at.SECTION_REGISTER:
+    case Actions["b" /* actionTypes */].SECTION_REGISTER:
       // If section exists in prevState, update it
       newState = prevState.map(section => {
         if (section && section.id === action.data.id) {
           hasMatch = true;
           return Object.assign({}, section, action.data);
         }
         return section;
       });
@@ -542,17 +612,17 @@ function Sections(prevState = INITIAL_ST
           order = action.data.order !== undefined ? action.data.order : 0;
           index = 0;
         }
 
         const section = Object.assign({ title: "", rows: [], order, enabled: false }, action.data, { initialized });
         newState.splice(index, 0, section);
       }
       return newState;
-    case at.SECTION_UPDATE:
+    case Actions["b" /* actionTypes */].SECTION_UPDATE:
       newState = prevState.map(section => {
         if (section && section.id === action.data.id) {
           // If the action is updating rows, we should consider initialized to be true.
           // This can be overridden if initialized is defined in the action.data
           const initialized = action.data.rows ? { initialized: true } : {};
           return Object.assign({}, section, initialized, action.data);
         }
         return section;
@@ -574,30 +644,30 @@ function Sections(prevState = INITIAL_ST
             return Object.assign({}, section, { rows: dedupedRows });
           }
 
           return section;
         });
       });
 
       return newState;
-    case at.SECTION_UPDATE_CARD:
+    case Actions["b" /* actionTypes */].SECTION_UPDATE_CARD:
       return prevState.map(section => {
         if (section && section.id === action.data.id && section.rows) {
           const newRows = section.rows.map(card => {
             if (card.url === action.data.url) {
               return Object.assign({}, card, action.data.options);
             }
             return card;
           });
           return Object.assign({}, section, { rows: newRows });
         }
         return section;
       });
-    case at.PLACES_BOOKMARK_ADDED:
+    case Actions["b" /* actionTypes */].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) {
             const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
@@ -606,17 +676,17 @@ function Sections(prevState = INITIAL_ST
               bookmarkTitle,
               bookmarkDateCreated: dateAdded,
               type: "bookmark"
             });
           }
           return item;
         })
       }));
-    case at.PLACES_BOOKMARK_REMOVED:
+    case Actions["b" /* actionTypes */].PLACES_BOOKMARK_REMOVED:
       if (!action.data) {
         return prevState;
       }
       return prevState.map(section => Object.assign({}, section, {
         rows: section.rows.map(item => {
           // find the bookmark within the rows that is attempted to be removed
           if (item.url === action.data.url) {
             const newSite = Object.assign({}, item);
@@ -626,65 +696,57 @@ function Sections(prevState = INITIAL_ST
             if (!newSite.type || newSite.type === "bookmark") {
               newSite.type = "history";
             }
             return newSite;
           }
           return item;
         })
       }));
-    case at.PLACES_LINKS_DELETED:
+    case Actions["b" /* actionTypes */].PLACES_LINKS_DELETED:
       return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => !action.data.includes(site.url)) }));
-    case at.PLACES_LINK_BLOCKED:
+    case Actions["b" /* actionTypes */].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(prevState = INITIAL_STATE.Snippets, action) {
   switch (action.type) {
-    case at.SNIPPETS_DATA:
+    case Actions["b" /* actionTypes */].SNIPPETS_DATA:
       return Object.assign({}, prevState, { initialized: true }, action.data);
-    case at.SNIPPETS_RESET:
+    case Actions["b" /* actionTypes */].SNIPPETS_RESET:
       return INITIAL_STATE.Snippets;
     default:
       return prevState;
   }
 }
 
 function PreferencesPane(prevState = INITIAL_STATE.PreferencesPane, action) {
   switch (action.type) {
-    case at.SETTINGS_OPEN:
+    case Actions["b" /* actionTypes */].SETTINGS_OPEN:
       return Object.assign({}, prevState, { visible: true });
-    case at.SETTINGS_CLOSE:
+    case Actions["b" /* actionTypes */].SETTINGS_CLOSE:
       return Object.assign({}, prevState, { visible: false });
     default:
       return prevState;
   }
 }
 
 var reducers = { TopSites, App, Snippets, Prefs, Dialog, Sections, PreferencesPane };
-module.exports = {
-  reducers,
-  INITIAL_STATE,
-  insertPinned,
-  TOP_SITES_DEFAULT_LENGTH,
-  TOP_SITES_SHOWMORE_LENGTH
-};
 
 /***/ }),
 /* 6 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 
 // EXTERNAL MODULE: ./system-addon/common/Actions.jsm
 var Actions = __webpack_require__(0);
-var Actions_default = /*#__PURE__*/__webpack_require__.n(Actions);
 
 // EXTERNAL MODULE: external "React"
 var external__React_ = __webpack_require__(1);
 var external__React__default = /*#__PURE__*/__webpack_require__.n(external__React_);
 
 // CONCATENATED MODULE: ./system-addon/content-src/components/ContextMenu/ContextMenu.jsx
 
 
@@ -778,115 +840,115 @@ var external__ReactIntl__default = /*#__
  * LinkMenu. All functions take the site as the first parameter, and optionally
  * the index of the site.
  */
 const LinkMenuOptions = {
   Separator: () => ({ type: "separator" }),
   RemoveBookmark: site => ({
     id: "menu_action_remove_bookmark",
     icon: "bookmark-added",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].DELETE_BOOKMARK_BY_ID,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].DELETE_BOOKMARK_BY_ID,
       data: site.bookmarkGuid
     }),
     userEvent: "BOOKMARK_DELETE"
   }),
   AddBookmark: site => ({
     id: "menu_action_bookmark",
     icon: "bookmark-hollow",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].BOOKMARK_URL,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].BOOKMARK_URL,
       data: { url: site.url, title: site.title, type: site.type }
     }),
     userEvent: "BOOKMARK_ADD"
   }),
   OpenInNewWindow: site => ({
     id: "menu_action_open_new_window",
     icon: "new-window",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].OPEN_NEW_WINDOW,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].OPEN_NEW_WINDOW,
       data: { url: site.url, referrer: site.referrer }
     }),
     userEvent: "OPEN_NEW_WINDOW"
   }),
   OpenInPrivateWindow: site => ({
     id: "menu_action_open_private_window",
     icon: "new-window-private",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].OPEN_PRIVATE_WINDOW,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].OPEN_PRIVATE_WINDOW,
       data: { url: site.url, referrer: site.referrer }
     }),
     userEvent: "OPEN_PRIVATE_WINDOW"
   }),
   BlockUrl: (site, index, eventSource) => ({
     id: "menu_action_dismiss",
     icon: "dismiss",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].BLOCK_URL,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].BLOCK_URL,
       data: site.url
     }),
-    impression: Actions["actionCreators"].ImpressionStats({
+    impression: Actions["a" /* actionCreators */].ImpressionStats({
       source: eventSource,
       block: 0,
       tiles: [{ id: site.guid, pos: index }]
     }),
     userEvent: "BLOCK"
   }),
   DeleteUrl: site => ({
     id: "menu_action_delete",
     icon: "delete",
     action: {
-      type: Actions["actionTypes"].DIALOG_OPEN,
+      type: Actions["b" /* actionTypes */].DIALOG_OPEN,
       data: {
-        onConfirm: [Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].DELETE_HISTORY_URL, data: { url: site.url, forceBlock: site.bookmarkGuid } }), Actions["actionCreators"].UserEvent({ event: "DELETE" })],
+        onConfirm: [Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].DELETE_HISTORY_URL, data: { url: site.url, forceBlock: site.bookmarkGuid } }), Actions["a" /* actionCreators */].UserEvent({ event: "DELETE" })],
         body_string_id: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
         confirm_button_string_id: "menu_action_delete",
         cancel_button_string_id: "topsites_form_cancel_button",
         icon: "modal-delete"
       }
     },
     userEvent: "DIALOG_OPEN"
   }),
   PinTopSite: (site, index) => ({
     id: "menu_action_pin",
     icon: "pin",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].TOP_SITES_PIN,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].TOP_SITES_PIN,
       data: { site: { url: site.url }, index }
     }),
     userEvent: "PIN"
   }),
   UnpinTopSite: site => ({
     id: "menu_action_unpin",
     icon: "unpin",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].TOP_SITES_UNPIN,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].TOP_SITES_UNPIN,
       data: { site: { url: site.url } }
     }),
     userEvent: "UNPIN"
   }),
   SaveToPocket: (site, index, eventSource) => ({
     id: "menu_action_save_to_pocket",
     icon: "pocket",
-    action: Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].SAVE_TO_POCKET,
+    action: Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].SAVE_TO_POCKET,
       data: { site: { url: site.url, title: site.title } }
     }),
-    impression: Actions["actionCreators"].ImpressionStats({
+    impression: Actions["a" /* actionCreators */].ImpressionStats({
       source: eventSource,
       pocket: 0,
       tiles: [{ id: site.guid, pos: index }]
     }),
     userEvent: "SAVE_TO_POCKET"
   }),
   EditTopSite: (site, index) => ({
     id: "edit_topsites_button_text",
     icon: "edit",
     action: {
-      type: Actions["actionTypes"].TOP_SITES_EDIT,
+      type: Actions["b" /* actionTypes */].TOP_SITES_EDIT,
       data: { index }
     }
   }),
   CheckBookmark: site => site.bookmarkGuid ? LinkMenuOptions.RemoveBookmark(site) : LinkMenuOptions.AddBookmark(site),
   CheckPinTopSite: (site, index) => site.isPinned ? LinkMenuOptions.UnpinTopSite(site) : LinkMenuOptions.PinTopSite(site, index)
 };
 // CONCATENATED MODULE: ./system-addon/content-src/components/LinkMenu/LinkMenu.jsx
 
@@ -907,17 +969,17 @@ class LinkMenu__LinkMenu extends externa
 
     const options = propOptions.map(o => LinkMenuOptions[o](site, index, source)).map(option => {
       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(Actions["actionCreators"].UserEvent({
+            props.dispatch(Actions["a" /* actionCreators */].UserEvent({
               event: userEvent,
               source,
               action_position: index
             }));
           }
           if (impression && props.shouldSendImpressionStats) {
             props.dispatch(impression);
           }
@@ -948,17 +1010,16 @@ const LinkMenu = Object(external__ReactI
 
 
 /***/ }),
 /* 7 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl__ = __webpack_require__(2);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_react_intl__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react__ = __webpack_require__(1);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_react__);
 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; };
 
 
 
@@ -1002,18 +1063,18 @@ class Info extends __WEBPACK_IMPORTED_MO
   }
   onInfoLeave(event) {
     // We currently have an active (true) info state, so keep it true only if we
     // have a related event target that is contained "within" the current target
     // (section-info-option) as itself or a descendant. Set to false otherwise.
     this._setInfoState(event && event.relatedTarget && (event.relatedTarget === event.currentTarget || event.relatedTarget.compareDocumentPosition(event.currentTarget) & Node.DOCUMENT_POSITION_CONTAINS));
   }
   onManageClick() {
-    this.props.dispatch({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SETTINGS_OPEN });
-    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
+    this.props.dispatch({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SETTINGS_OPEN });
+    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
   }
   render() {
     const { infoOption, intl } = this.props;
     const infoOptionIconA11yAttrs = {
       "aria-haspopup": "true",
       "aria-controls": "info-option",
       "aria-expanded": this.state.infoActive ? "true" : "false",
       "role": "note",
@@ -1070,18 +1131,18 @@ const InfoIntl = Object(__WEBPACK_IMPORT
 
 class Disclaimer extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.PureComponent {
   constructor(props) {
     super(props);
     this.onAcknowledge = this.onAcknowledge.bind(this);
   }
 
   onAcknowledge() {
-    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SetPref(this.props.disclaimerPref, false));
-    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].UserEvent({ event: "SECTION_DISCLAIMER_ACKNOWLEDGED", source: this.props.eventSource }));
+    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SetPref(this.props.disclaimerPref, false));
+    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].UserEvent({ event: "SECTION_DISCLAIMER_ACKNOWLEDGED", source: this.props.eventSource }));
   }
 
   render() {
     const disclaimer = this.props.disclaimer;
     return __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
       "div",
       { className: "section-disclaimer" },
       __WEBPACK_IMPORTED_MODULE_2_react___default.a.createElement(
@@ -1165,17 +1226,17 @@ class _CollapsibleSection extends __WEBP
     this._setInfoState(event && event.relatedTarget && (event.relatedTarget === event.currentTarget || event.relatedTarget.compareDocumentPosition(event.currentTarget) & Node.DOCUMENT_POSITION_CONTAINS));
   }
   onHeaderClick() {
     // Get the current height of the body so max-height transitions can work
     this.setState({
       isAnimating: true,
       maxHeight: `${this.sectionBody.scrollHeight}px`
     });
-    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SetPref(this.props.prefName, !getCollapsed(this.props)));
+    this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SetPref(this.props.prefName, !getCollapsed(this.props)));
   }
   onTransitionEnd(event) {
     // Only update the animating state for our own transition (not a child's)
     if (event.target === event.currentTarget) {
       this.setState({ isAnimating: false });
     }
   }
   renderIcon() {
@@ -1242,34 +1303,32 @@ const CollapsibleSection = Object(__WEBP
 /* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
 
 /***/ }),
 /* 8 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__ = __webpack_require__(9);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react__ = __webpack_require__(1);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_react__);
 
 
 
 
 // Currently record only a fixed set of sections. This will prevent data
 // from custom sections from showing up or from topstories.
 const RECORDED_SECTIONS = ["highlights", "topsites"];
 
 class ComponentPerfTimer extends __WEBPACK_IMPORTED_MODULE_2_react___default.a.Component {
   constructor(props) {
     super(props);
     // Just for test dependency injection:
-    this.perfSvc = this.props.perfSvc || __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__["perfService"];
+    this.perfSvc = this.props.perfSvc || __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__["a" /* perfService */];
 
     this._sendBadStateEvent = this._sendBadStateEvent.bind(this);
     this._sendPaintedEvent = this._sendPaintedEvent.bind(this);
     this._reportMissingData = false;
     this._timestampHandled = false;
     this._recordedFirstRender = false;
   }
 
@@ -1365,18 +1424,18 @@ class ComponentPerfTimer extends __WEBPA
     // highlights_data_ready_ts, topsites_data_ready_ts.
     const dataReadyKey = `${this.props.id}_data_ready_ts`;
     this.perfSvc.mark(dataReadyKey);
 
     try {
       const firstRenderKey = `${this.props.id}_first_render_ts`;
       // value has to be Int32.
       const value = parseInt(this.perfSvc.getMostRecentAbsMarkStartByName(dataReadyKey) - this.perfSvc.getMostRecentAbsMarkStartByName(firstRenderKey), 10);
-      this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({
-        type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SAVE_SESSION_PERF_DATA,
+      this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({
+        type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
         // highlights_data_late_by_ms, topsites_data_late_by_ms.
         data: { [`${this.props.id}_data_late_by_ms`]: value }
       }));
     } catch (ex) {
       // If this failed, it's likely because the `privacy.resistFingerprinting`
       // pref is true.
     }
   }
@@ -1390,18 +1449,18 @@ class ComponentPerfTimer extends __WEBPA
     // topsites_first_painted_ts.
     const key = `${this.props.id}_first_painted_ts`;
     this.perfSvc.mark(key);
 
     try {
       const data = {};
       data[key] = this.perfSvc.getMostRecentAbsMarkStartByName(key);
 
-      this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({
-        type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SAVE_SESSION_PERF_DATA,
+      this.props.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({
+        type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
         data
       }));
     } catch (ex) {
       // If this failed, it's likely because the `privacy.resistFingerprinting`
       // pref is true.  We should at least not blow up, and should continue
       // to set this._timestampHandled to avoid going through this again.
     }
   }
@@ -1414,19 +1473,21 @@ class ComponentPerfTimer extends __WEBPA
     return this.props.children;
   }
 }
 /* harmony export (immutable) */ __webpack_exports__["a"] = ComponentPerfTimer;
 
 
 /***/ }),
 /* 9 */
-/***/ (function(module, exports, __webpack_require__) {
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
+/* unused harmony export _PerfService */
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return perfService; });
 /* globals Services */
 
 
 /* istanbul ignore if */
 // Note: normally we would just feature detect Components.utils here, but
 // unfortunately that throws an ugly warning in content if we do.
 
 if (typeof Window === "undefined" && typeof Components !== "undefined" && Components.utils) {
@@ -1448,25 +1509,26 @@ if (typeof Services !== "undefined") {
   // This is a dummy object so this file doesn't crash in the node prerendering
   // task.
   usablePerfObj = {
     now() {},
     mark() {}
   };
 }
 
-var _PerfService = function _PerfService(options) {
+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 {
     this._perf = usablePerfObj;
   }
-};
+}
+
 
 _PerfService.prototype = {
   /**
    * Calls the underlying mark() method on the appropriate Window.performance
    * object to add a mark with the given name to the appropriate performance
    * timeline.
    *
    * @param  {String} name  the name to give the current mark
@@ -1544,60 +1606,54 @@ var _PerfService = function _PerfService
     }
 
     let mostRecentEntry = entries[entries.length - 1];
     return this._perf.timeOrigin + mostRecentEntry.startTime;
   }
 };
 
 var perfService = new _PerfService();
-module.exports = {
-  _PerfService,
-  perfService
-};
 
 /***/ }),
 /* 10 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_content_src_lib_snippets__ = __webpack_require__(11);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_content_src_components_Base_Base__ = __webpack_require__(12);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_content_src_lib_detect_user_session_start__ = __webpack_require__(19);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__ = __webpack_require__(20);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_content_src_lib_detect_user_session_start__ = __webpack_require__(17);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__ = __webpack_require__(18);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux__ = __webpack_require__(3);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5_react_redux__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react__ = __webpack_require__(1);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6_react__);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_react_dom__ = __webpack_require__(22);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_react_dom__ = __webpack_require__(20);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_react_dom___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_7_react_dom__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__ = __webpack_require__(5);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__);
-
-
-
-
-
-
-
-
-
-
-const store = Object(__WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__["a" /* initStore */])(__WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__["reducers"], global.gActivityStreamPrerenderedState);
+
+
+
+
+
+
+
+
+
+
+const store = Object(__WEBPACK_IMPORTED_MODULE_4_content_src_lib_init_store__["a" /* initStore */])(__WEBPACK_IMPORTED_MODULE_8_common_Reducers_jsm__["c" /* reducers */], global.gActivityStreamPrerenderedState);
 
 new __WEBPACK_IMPORTED_MODULE_3_content_src_lib_detect_user_session_start__["a" /* DetectUserSessionStart */](store).sendEventOrAddListener();
 
 // 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(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].NEW_TAB_STATE_REQUEST }));
+  store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
 }
 
 __WEBPACK_IMPORTED_MODULE_7_react_dom___default.a.render(__WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(
   __WEBPACK_IMPORTED_MODULE_5_react_redux__["Provider"],
   { store: store },
   __WEBPACK_IMPORTED_MODULE_6_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_2_content_src_components_Base_Base__["a" /* Base */], {
     isPrerendered: !!global.gActivityStreamPrerenderedState,
     locale: global.document.documentElement.lang,
@@ -1609,17 +1665,16 @@ Object(__WEBPACK_IMPORTED_MODULE_1_conte
 
 /***/ }),
 /* 11 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (immutable) */ __webpack_exports__["a"] = addSnippetsSubscriber;
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
 const DATABASE_NAME = "snippets_db";
 const DATABASE_VERSION = 1;
 const SNIPPETS_OBJECTSTORE_NAME = "snippets";
 const SNIPPETS_UPDATE_INTERVAL_MS = 14400000;
 /* unused harmony export SNIPPETS_UPDATE_INTERVAL_MS */
  // 4 hours.
 
 const SNIPPETS_ENABLED_EVENT = "Snippets:Enabled";
@@ -1670,27 +1725,27 @@ class SnippetsMap extends Map {
    */
   async blockSnippetById(id) {
     if (!id) {
       return;
     }
     let blockList = this.blockList;
     if (!blockList.includes(id)) {
       blockList.push(id);
-      this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SNIPPETS_BLOCKLIST_UPDATED, data: blockList }));
+      this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SNIPPETS_BLOCKLIST_UPDATED, data: blockList }));
       await this.set("blockList", blockList);
     }
   }
 
   disableOnboarding() {
-    this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].DISABLE_ONBOARDING }));
+    this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].DISABLE_ONBOARDING }));
   }
 
   showFirefoxAccounts() {
-    this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SHOW_FIREFOX_ACCOUNTS }));
+    this._dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SHOW_FIREFOX_ACCOUNTS }));
   }
 
   /**
    * connect - Attaches an indexedDB back-end to the Map so that any set values
    *           are also cached in a store. It also restores any existing values
    *           that are already stored in the indexedDB store.
    *
    * @return {type}  description
@@ -1876,17 +1931,17 @@ class SnippetsProvider {
     for (const scriptEl of snippetsEl.getElementsByTagName("script")) {
       const relocatedScript = document.createElement("script");
       relocatedScript.text = scriptEl.text;
       scriptEl.parentNode.replaceChild(relocatedScript, scriptEl);
     }
   }
 
   _onAction(msg) {
-    if (msg.data.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SNIPPET_BLOCKED) {
+    if (msg.data.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SNIPPET_BLOCKED) {
       this.snippetsMap.set("blockList", msg.data.data);
       document.getElementById("snippets-container").style.display = "none";
     }
   }
 
   /**
    * init - Fetch the snippet payload and show snippets
    *
@@ -1988,17 +2043,16 @@ function addSnippetsSubscriber(store) {
 /***/ }),
 /* 12 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 
 // EXTERNAL MODULE: ./system-addon/common/Actions.jsm
 var Actions = __webpack_require__(0);
-var Actions_default = /*#__PURE__*/__webpack_require__.n(Actions);
 
 // EXTERNAL MODULE: external "ReactIntl"
 var external__ReactIntl_ = __webpack_require__(2);
 var external__ReactIntl__default = /*#__PURE__*/__webpack_require__.n(external__ReactIntl_);
 
 // EXTERNAL MODULE: external "ReactRedux"
 var external__ReactRedux_ = __webpack_require__(3);
 var external__ReactRedux__default = /*#__PURE__*/__webpack_require__.n(external__ReactRedux_);
@@ -2035,18 +2089,18 @@ var external__React__default = /*#__PURE
 class ConfirmDialog__ConfirmDialog extends external__React__default.a.PureComponent {
   constructor(props) {
     super(props);
     this._handleCancelBtn = this._handleCancelBtn.bind(this);
     this._handleConfirmBtn = this._handleConfirmBtn.bind(this);
   }
 
   _handleCancelBtn() {
-    this.props.dispatch({ type: Actions["actionTypes"].DIALOG_CANCEL });
-    this.props.dispatch(Actions["actionCreators"].UserEvent({ event: Actions["actionTypes"].DIALOG_CANCEL }));
+    this.props.dispatch({ type: Actions["b" /* actionTypes */].DIALOG_CANCEL });
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: Actions["b" /* actionTypes */].DIALOG_CANCEL }));
   }
 
   _handleConfirmBtn() {
     this.props.data.onConfirm.forEach(this.props.dispatch);
   }
 
   _renderModalMessage() {
     const message_body = this.props.data.body_string_id;
@@ -2120,23 +2174,23 @@ const ConfirmDialog = Object(external__R
  */
 class ManualMigration__ManualMigration extends external__React__default.a.PureComponent {
   constructor(props) {
     super(props);
     this.onLaunchTour = this.onLaunchTour.bind(this);
     this.onCancelTour = this.onCancelTour.bind(this);
   }
   onLaunchTour() {
-    this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].MIGRATION_START }));
-    this.props.dispatch(Actions["actionCreators"].UserEvent({ event: Actions["actionTypes"].MIGRATION_START }));
+    this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].MIGRATION_START }));
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: Actions["b" /* actionTypes */].MIGRATION_START }));
   }
 
   onCancelTour() {
-    this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].MIGRATION_CANCEL }));
-    this.props.dispatch(Actions["actionCreators"].UserEvent({ event: Actions["actionTypes"].MIGRATION_CANCEL }));
+    this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].MIGRATION_CANCEL }));
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: Actions["b" /* actionTypes */].MIGRATION_CANCEL }));
   }
 
   render() {
     return external__React__default.a.createElement(
       "div",
       { className: "manual-migration-container" },
       external__React__default.a.createElement(
         "p",
@@ -2158,19 +2212,18 @@ class ManualMigration__ManualMigration e
           external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "manual_migration_import_button" })
         )
       )
     );
   }
 }
 
 const ManualMigration = Object(external__ReactRedux_["connect"])()(ManualMigration__ManualMigration);
-// EXTERNAL MODULE: ./system-addon/common/Reducers.jsm
+// EXTERNAL MODULE: ./system-addon/common/Reducers.jsm + 1 modules
 var Reducers = __webpack_require__(5);
-var Reducers_default = /*#__PURE__*/__webpack_require__.n(Reducers);
 
 // CONCATENATED MODULE: ./system-addon/content-src/components/PreferencesPane/PreferencesPane.jsx
 
 
 
 
 
 
@@ -2229,33 +2282,33 @@ class PreferencesPane__PreferencesPane e
       this.togglePane();
     }
   }
   handlePrefChange(event) {
     const target = event.target;
     const { name, checked } = target;
     let value = checked;
     if (name === "topSitesCount") {
-      value = checked ? Reducers["TOP_SITES_SHOWMORE_LENGTH"] : Reducers["TOP_SITES_DEFAULT_LENGTH"];
+      value = checked ? Reducers["b" /* TOP_SITES_SHOWMORE_LENGTH */] : Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */];
     }
-    this.props.dispatch(Actions["actionCreators"].SetPref(name, value));
+    this.props.dispatch(Actions["a" /* actionCreators */].SetPref(name, value));
   }
   handleSectionChange(event) {
     const target = event.target;
     const id = target.name;
-    const type = target.checked ? Actions["actionTypes"].SECTION_ENABLE : Actions["actionTypes"].SECTION_DISABLE;
-    this.props.dispatch(Actions["actionCreators"].SendToMain({ type, data: id }));
+    const type = target.checked ? Actions["b" /* actionTypes */].SECTION_ENABLE : Actions["b" /* actionTypes */].SECTION_DISABLE;
+    this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type, data: id }));
   }
   togglePane() {
     if (this.isSidebarOpen()) {
-      this.props.dispatch({ type: Actions["actionTypes"].SETTINGS_CLOSE });
-      this.props.dispatch(Actions["actionCreators"].UserEvent({ event: "CLOSE_NEWTAB_PREFS" }));
+      this.props.dispatch({ type: Actions["b" /* actionTypes */].SETTINGS_CLOSE });
+      this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: "CLOSE_NEWTAB_PREFS" }));
     } else {
-      this.props.dispatch({ type: Actions["actionTypes"].SETTINGS_OPEN });
-      this.props.dispatch(Actions["actionCreators"].UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
+      this.props.dispatch({ type: Actions["b" /* actionTypes */].SETTINGS_OPEN });
+      this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: "OPEN_NEWTAB_PREFS" }));
     }
   }
   onWrapperMount(wrapper) {
     this.wrapper = wrapper;
   }
   render() {
     const props = this.props;
     const prefs = props.Prefs.values;
@@ -2307,17 +2360,17 @@ class PreferencesPane__PreferencesPane e
                 value: prefs.showTopSites,
                 onChange: this.handlePrefChange,
                 titleString: { id: "settings_pane_topsites_header" },
                 descString: { id: "settings_pane_topsites_body" } },
               external__React__default.a.createElement(PreferencesInput, {
                 className: "showMoreTopSites",
                 prefName: "topSitesCount",
                 disabled: !prefs.showTopSites,
-                value: prefs.topSitesCount !== Reducers["TOP_SITES_DEFAULT_LENGTH"],
+                value: prefs.topSitesCount !== Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */],
                 onChange: this.handlePrefChange,
                 titleString: { id: "settings_pane_topsites_options_showmore" },
                 labelClassName: "icon icon-topsites" })
             ),
             sections.filter(section => !section.shouldHidePref).map(({ id, title, enabled, pref }) => external__React__default.a.createElement(
               PreferencesInput,
               {
                 key: id,
@@ -2357,22 +2410,102 @@ class PreferencesPane__PreferencesPane e
   }
 }
 
 const PreferencesPane = Object(external__ReactRedux_["connect"])(state => ({
   Prefs: state.Prefs,
   PreferencesPane: state.PreferencesPane,
   Sections: state.Sections
 }))(Object(external__ReactIntl_["injectIntl"])(PreferencesPane__PreferencesPane));
-// EXTERNAL MODULE: ./system-addon/common/PrerenderData.jsm
-var PrerenderData = __webpack_require__(14);
-var PrerenderData_default = /*#__PURE__*/__webpack_require__.n(PrerenderData);
-
+// CONCATENATED MODULE: ./system-addon/common/PrerenderData.jsm
+class _PrerenderData {
+  constructor(options) {
+    this.initialPrefs = options.initialPrefs;
+    this.initialSections = options.initialSections;
+    this._setValidation(options.validation);
+  }
+
+  get validation() {
+    return this._validation;
+  }
+
+  set validation(value) {
+    this._setValidation(value);
+  }
+
+  get invalidatingPrefs() {
+    return this._invalidatingPrefs;
+  }
+
+  // This is needed so we can use it in the constructor
+  _setValidation(value = []) {
+    this._validation = value;
+    this._invalidatingPrefs = value.reduce((result, next) => {
+      if (typeof next === "string") {
+        result.push(next);
+        return result;
+      } else if (next && next.oneOf) {
+        return result.concat(next.oneOf);
+      }
+      throw new Error("Your validation configuration is not properly configured");
+    }, []);
+  }
+
+  arePrefsValid(getPref) {
+    for (const prefs of this.validation) {
+      // {oneOf: ["foo", "bar"]}
+      if (prefs && prefs.oneOf && !prefs.oneOf.some(name => getPref(name) === this.initialPrefs[name])) {
+        return false;
+
+        // "foo"
+      } else if (getPref(prefs) !== this.initialPrefs[prefs]) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
+var PrerenderData = new _PrerenderData({
+  initialPrefs: {
+    "migrationExpired": true,
+    "showTopSites": true,
+    "showSearch": true,
+    "topSitesCount": 12,
+    "collapseTopSites": false,
+    "section.highlights.collapsed": false,
+    "section.topstories.collapsed": false,
+    "feeds.section.topstories": true,
+    "feeds.section.highlights": true
+  },
+  // Prefs listed as invalidating 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.
+  validation: ["showTopSites", "showSearch", "topSitesCount", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
+  // This means if either of these are set to their default values,
+  // prerendering can be used.
+  { oneOf: ["feeds.section.topstories", "feeds.section.highlights"] }],
+  initialSections: [{
+    enabled: true,
+    icon: "pocket",
+    id: "topstories",
+    order: 1,
+    title: { id: "header_recommended_by", values: { provider: "Pocket" } }
+  }, {
+    enabled: true,
+    id: "highlights",
+    icon: "highlights",
+    order: 2,
+    title: { id: "header_highlights" }
+  }]
+});
 // EXTERNAL MODULE: ./system-addon/content-src/lib/constants.js
-var constants = __webpack_require__(15);
+var constants = __webpack_require__(13);
 
 // CONCATENATED MODULE: ./system-addon/content-src/components/Search/Search.jsx
 /* globals ContentSearchUIController */
 
 
 
 
 
@@ -2384,17 +2517,17 @@ class Search__Search extends external__R
     super(props);
     this.onClick = this.onClick.bind(this);
     this.onInputMount = this.onInputMount.bind(this);
   }
 
   handleEvent(event) {
     // Also track search events with our own telemetry
     if (event.detail.type === "Search") {
-      this.props.dispatch(Actions["actionCreators"].UserEvent({ event: "SEARCH" }));
+      this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({ event: "SEARCH" }));
     }
   }
   onClick(event) {
     window.gContentSearchController.search(event);
   }
   componentWillUnmount() {
     delete window.gContentSearchController;
   }
@@ -2464,17 +2597,17 @@ class Search__Search extends external__R
         )
       )
     );
   }
 }
 
 const Search = Object(external__ReactRedux_["connect"])()(Object(external__ReactIntl_["injectIntl"])(Search__Search));
 // EXTERNAL MODULE: ./system-addon/content-src/components/Sections/Sections.jsx
-var Sections = __webpack_require__(16);
+var Sections = __webpack_require__(14);
 
 // CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSitesConstants.js
 const TOP_SITES_SOURCE = "TOP_SITES";
 const TOP_SITES_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"];
 // minimum size necessary to show a rich icon instead of a screenshot
 const MIN_RICH_FAVICON_SIZE = 96;
 // minimum size necessary to show any icon in the top left corner with a screenshot
 const MIN_CORNER_FAVICON_SIZE = 16;
@@ -2626,17 +2759,17 @@ class TopSite_TopSite extends external__
   }
   toggleContextMenu(event, index) {
     this.setState({
       activeTile: index,
       showContextMenu: true
     });
   }
   userEvent(event) {
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       event,
       source: TOP_SITES_SOURCE,
       action_position: this.props.index
     }));
   }
   onLinkClick(ev) {
     if (this.props.onEdit) {
       // Ignore clicks if we are in the edit modal.
@@ -2650,38 +2783,38 @@ class TopSite_TopSite extends external__
     this.toggleContextMenu(event, this.props.index);
   }
   onMenuUpdate(showContextMenu) {
     this.setState({ showContextMenu });
   }
   onDismissButtonClick() {
     const { link } = this.props;
     if (link.isPinned) {
-      this.props.dispatch(Actions["actionCreators"].SendToMain({
-        type: Actions["actionTypes"].TOP_SITES_UNPIN,
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+        type: Actions["b" /* actionTypes */].TOP_SITES_UNPIN,
         data: { site: { url: link.url } }
       }));
     }
-    this.props.dispatch(Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].BLOCK_URL,
+    this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].BLOCK_URL,
       data: link.url
     }));
     this.userEvent("BLOCK");
   }
   onPinButtonClick() {
     const { link, index } = this.props;
     if (link.isPinned) {
-      this.props.dispatch(Actions["actionCreators"].SendToMain({
-        type: Actions["actionTypes"].TOP_SITES_UNPIN,
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+        type: Actions["b" /* actionTypes */].TOP_SITES_UNPIN,
         data: { site: { url: link.url } }
       }));
       this.userEvent("UNPIN");
     } else {
-      this.props.dispatch(Actions["actionCreators"].SendToMain({
-        type: Actions["actionTypes"].TOP_SITES_PIN,
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+        type: Actions["b" /* actionTypes */].TOP_SITES_PIN,
         data: { site: { url: link.url }, index }
       }));
       this.userEvent("PIN");
     }
   }
   onEditButtonClick() {
     this.props.onEdit(this.props.index);
   }
@@ -2749,17 +2882,17 @@ TopSite_TopSite.defaultProps = {
 
 class TopSite_TopSitePlaceholder extends external__React__default.a.PureComponent {
   constructor(props) {
     super(props);
     this.onEditButtonClick = this.onEditButtonClick.bind(this);
   }
   onEditButtonClick() {
     this.props.dispatch({
-      type: Actions["actionTypes"].TOP_SITES_EDIT,
+      type: Actions["b" /* actionTypes */].TOP_SITES_EDIT,
       data: { index: this.props.index }
     });
   }
   render() {
     return external__React__default.a.createElement(
       TopSite_TopSiteLink,
       _extends({ className: "placeholder", isDraggable: false }, this.props),
       external__React__default.a.createElement("button", { className: "context-menu-button edit-button icon",
@@ -2786,17 +2919,17 @@ class TopSite__TopSiteList extends exter
       const newTopSites = nextProps.TopSites && nextProps.TopSites.rows;
       if (prevTopSites && prevTopSites[this.state.draggedIndex] && prevTopSites[this.state.draggedIndex].url === this.state.draggedSite.url && (!newTopSites[this.state.draggedIndex] || newTopSites[this.state.draggedIndex].url !== this.state.draggedSite.url)) {
         // We got the new order from the redux store via props. We can clear state now.
         this.setState(this.DEFAULT_STATE);
       }
     }
   }
   userEvent(event, index) {
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       event,
       source: TOP_SITES_SOURCE,
       action_position: index
     }));
   }
   onDragEvent(event, index, link, title) {
     switch (event.type) {
       case "dragstart":
@@ -2814,18 +2947,18 @@ class TopSite__TopSiteList extends exter
         if (index === this.state.draggedIndex) {
           this.setState({ topSitesPreview: null });
         } else {
           this.setState({ topSitesPreview: this._makeTopSitesPreview(index) });
         }
         break;
       case "drop":
         if (index !== this.state.draggedIndex) {
-          this.props.dispatch(Actions["actionCreators"].SendToMain({
-            type: Actions["actionTypes"].TOP_SITES_INSERT,
+          this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+            type: Actions["b" /* actionTypes */].TOP_SITES_INSERT,
             data: { site: { url: this.state.draggedSite.url, label: this.state.draggedTitle }, index }
           }));
           this.userEvent("DROP", index);
         }
         break;
     }
   }
   _getTopSites() {
@@ -2952,39 +3085,39 @@ class TopSiteForm_TopSiteForm extends ex
   }
   onAddButtonClick(ev) {
     ev.preventDefault();
     if (this.validateForm()) {
       let site = { url: this.cleanUrl() };
       if (this.state.label !== "") {
         site.label = this.state.label;
       }
-      this.props.dispatch(Actions["actionCreators"].SendToMain({
-        type: Actions["actionTypes"].TOP_SITES_INSERT,
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+        type: Actions["b" /* actionTypes */].TOP_SITES_INSERT,
         data: { site }
       }));
-      this.props.dispatch(Actions["actionCreators"].UserEvent({
+      this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
         source: TOP_SITES_SOURCE,
         event: "TOP_SITES_ADD"
       }));
       this.props.onClose();
     }
   }
   onSaveButtonClick(ev) {
     ev.preventDefault();
     if (this.validateForm()) {
       let site = { url: this.cleanUrl() };
       if (this.state.label !== "") {
         site.label = this.state.label;
       }
-      this.props.dispatch(Actions["actionCreators"].SendToMain({
-        type: Actions["actionTypes"].TOP_SITES_PIN,
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+        type: Actions["b" /* actionTypes */].TOP_SITES_PIN,
         data: { site, index: this.props.index }
       }));
-      this.props.dispatch(Actions["actionCreators"].UserEvent({
+      this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
         source: TOP_SITES_SOURCE,
         event: "TOP_SITES_EDIT",
         action_position: this.props.index
       }));
       this.props.onClose();
     }
   }
   cleanUrl() {
@@ -3113,54 +3246,54 @@ class TopSitesEdit__TopSitesEdit extends
     this.onModalOverlayClick = this.onModalOverlayClick.bind(this);
     this.onAddButtonClick = this.onAddButtonClick.bind(this);
     this.onFormClose = this.onFormClose.bind(this);
     this.onEdit = this.onEdit.bind(this);
   }
   onEditButtonClick() {
     this.setState({ showEditModal: !this.state.showEditModal });
     const event = this.state.showEditModal ? "TOP_SITES_EDIT_OPEN" : "TOP_SITES_EDIT_CLOSE";
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       source: TOP_SITES_SOURCE,
       event
     }));
   }
   onModalOverlayClick() {
     this.setState({ showEditModal: false, showAddForm: false, showEditForm: false });
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       source: TOP_SITES_SOURCE,
       event: "TOP_SITES_EDIT_CLOSE"
     }));
-    this.props.dispatch({ type: Actions["actionTypes"].TOP_SITES_CANCEL_EDIT });
+    this.props.dispatch({ type: Actions["b" /* actionTypes */].TOP_SITES_CANCEL_EDIT });
   }
   onShowMoreLessClick() {
-    const prefIsSetToDefault = this.props.TopSitesCount === Reducers["TOP_SITES_DEFAULT_LENGTH"];
-    this.props.dispatch(Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].SET_PREF,
-      data: { name: "topSitesCount", value: prefIsSetToDefault ? Reducers["TOP_SITES_SHOWMORE_LENGTH"] : Reducers["TOP_SITES_DEFAULT_LENGTH"] }
+    const prefIsSetToDefault = this.props.TopSitesCount === Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */];
+    this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].SET_PREF,
+      data: { name: "topSitesCount", value: prefIsSetToDefault ? Reducers["b" /* TOP_SITES_SHOWMORE_LENGTH */] : Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */] }
     }));
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       source: TOP_SITES_SOURCE,
       event: prefIsSetToDefault ? "TOP_SITES_EDIT_SHOW_MORE" : "TOP_SITES_EDIT_SHOW_LESS"
     }));
   }
   onAddButtonClick() {
     this.setState({ showAddForm: true });
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       source: TOP_SITES_SOURCE,
       event: "TOP_SITES_ADD_FORM_OPEN"
     }));
   }
   onFormClose() {
     this.setState({ showAddForm: false, showEditForm: false });
-    this.props.dispatch({ type: Actions["actionTypes"].TOP_SITES_CANCEL_EDIT });
+    this.props.dispatch({ type: Actions["b" /* actionTypes */].TOP_SITES_CANCEL_EDIT });
   }
   onEdit(index) {
     this.setState({ showEditForm: true, editIndex: index });
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       source: TOP_SITES_SOURCE,
       event: "TOP_SITES_EDIT_FORM_OPEN"
     }));
   }
   render() {
     const { editForm } = this.props.TopSites;
     const showEditForm = editForm && editForm.visible || this.state.showEditModal && this.state.showEditForm;
     let editIndex = this.state.editIndex;
@@ -3210,18 +3343,18 @@ class TopSitesEdit__TopSitesEdit extends
             { className: "actions" },
             external__React__default.a.createElement(
               "button",
               { className: "add", onClick: this.onAddButtonClick },
               external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "edit_topsites_add_button" })
             ),
             external__React__default.a.createElement(
               "button",
-              { className: `icon icon-topsites show-${this.props.TopSitesCount === Reducers["TOP_SITES_DEFAULT_LENGTH"] ? "more" : "less"}`, onClick: this.onShowMoreLessClick },
-              external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: `edit_topsites_show${this.props.TopSitesCount === Reducers["TOP_SITES_DEFAULT_LENGTH"] ? "more" : "less"}_button` })
+              { className: `icon icon-topsites show-${this.props.TopSitesCount === Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */] ? "more" : "less"}`, onClick: this.onShowMoreLessClick },
+              external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: `edit_topsites_show${this.props.TopSitesCount === Reducers["a" /* TOP_SITES_DEFAULT_LENGTH */] ? "more" : "less"}_button` })
             ),
             external__React__default.a.createElement(
               "button",
               { className: "done", onClick: this.onEditButtonClick },
               external__React__default.a.createElement(external__ReactIntl_["FormattedMessage"], { id: "edit_topsites_done_button" })
             )
           )
         )
@@ -3304,18 +3437,18 @@ class TopSites__TopSites extends externa
   /**
    * Dispatch session statistics about the quality of TopSites icons and pinned count.
    */
   _dispatchTopSitesStats() {
     const topSites = this._getTopSites();
     const topSitesIconsStats = countTopSitesIconsTypes(topSites);
     const topSitesPinned = topSites.filter(site => !!site.isPinned).length;
     // Dispatch telemetry event with the count of TopSites images types.
-    this.props.dispatch(Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].SAVE_SESSION_PERF_DATA,
+    this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
       data: { topsites_icon_stats: topSitesIconsStats, topsites_pinned: topSitesPinned }
     }));
   }
 
   /**
    * Return the TopSites to display based on prefs.
    */
   _getTopSites() {
@@ -3381,42 +3514,42 @@ class Base__Base extends external__React
     addLocaleDataForReactIntl(locale);
   }
 
   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(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].NEW_TAB_STATE_REQUEST }));
-      this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].PAGE_PRERENDERED }));
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].PAGE_PRERENDERED }));
     }
   }
 
   componentWillUpdate({ App }) {
     this.sendNewTabRehydrated(App);
   }
 
   // The NEW_TAB_REHYDRATED event is used to inform feeds that their
   // data has been consumed e.g. for counting the number of tabs that
   // have rendered that data.
   sendNewTabRehydrated(App) {
     if (App && App.initialized && !this.renderNotified) {
-      this.props.dispatch(Actions["actionCreators"].SendToMain({ type: Actions["actionTypes"].NEW_TAB_REHYDRATED, data: {} }));
+      this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({ type: Actions["b" /* actionTypes */].NEW_TAB_REHYDRATED, data: {} }));
       this.renderNotified = true;
     }
   }
 
   render() {
     const props = this.props;
     const { App, locale, strings } = props;
     const { initialized } = App;
     const prefs = props.Prefs.values;
 
-    const shouldBeFixedToTop = PrerenderData["PrerenderData"].arePrefsValid(name => prefs[name]);
+    const shouldBeFixedToTop = PrerenderData.arePrefsValid(name => prefs[name]);
 
     const outerClassName = `outer-wrapper${shouldBeFixedToTop ? " fixed-to-top" : ""}`;
 
     if (!props.isPrerendered && !initialized) {
       return null;
     }
 
     return external__React__default.a.createElement(
@@ -3447,174 +3580,40 @@ class Base__Base extends external__React
 
 
 const Base = Object(external__ReactRedux_["connect"])(state => ({ App: state.App, Prefs: state.Prefs }))(Base__Base);
 /* harmony export (immutable) */ __webpack_exports__["a"] = Base;
 
 
 /***/ }),
 /* 13 */
-/***/ (function(module, exports) {
-
-var Dedupe = class Dedupe {
-  constructor(createKey) {
-    this.createKey = createKey || this.defaultCreateKey;
-  }
-
-  defaultCreateKey(item) {
-    return item;
-  }
-
-  /**
-   * Dedupe any number of grouped elements favoring those from earlier groups.
-   *
-   * @param {Array} groups Contains an arbitrary number of arrays of elements.
-   * @returns {Array} A matching array of each provided group deduped.
-   */
-  group(...groups) {
-    const globalKeys = new Set();
-    const result = [];
-    for (const values of groups) {
-      const valueMap = new Map();
-      for (const value of values) {
-        const key = this.createKey(value);
-        if (!globalKeys.has(key) && !valueMap.has(key)) {
-          valueMap.set(key, value);
-        }
-      }
-      result.push(valueMap);
-      valueMap.forEach((value, key) => globalKeys.add(key));
-    }
-    return result.map(m => Array.from(m.values()));
-  }
-};
-module.exports = {
-  Dedupe
-};
-
-/***/ }),
-/* 14 */
-/***/ (function(module, exports) {
-
-class _PrerenderData {
-  constructor(options) {
-    this.initialPrefs = options.initialPrefs;
-    this.initialSections = options.initialSections;
-    this._setValidation(options.validation);
-  }
-
-  get validation() {
-    return this._validation;
-  }
-
-  set validation(value) {
-    this._setValidation(value);
-  }
-
-  get invalidatingPrefs() {
-    return this._invalidatingPrefs;
-  }
-
-  // This is needed so we can use it in the constructor
-  _setValidation(value = []) {
-    this._validation = value;
-    this._invalidatingPrefs = value.reduce((result, next) => {
-      if (typeof next === "string") {
-        result.push(next);
-        return result;
-      } else if (next && next.oneOf) {
-        return result.concat(next.oneOf);
-      }
-      throw new Error("Your validation configuration is not properly configured");
-    }, []);
-  }
-
-  arePrefsValid(getPref) {
-    for (const prefs of this.validation) {
-      // {oneOf: ["foo", "bar"]}
-      if (prefs && prefs.oneOf && !prefs.oneOf.some(name => getPref(name) === this.initialPrefs[name])) {
-        return false;
-
-        // "foo"
-      } else if (getPref(prefs) !== this.initialPrefs[prefs]) {
-        return false;
-      }
-    }
-    return true;
-  }
-}
-
-var PrerenderData = new _PrerenderData({
-  initialPrefs: {
-    "migrationExpired": true,
-    "showTopSites": true,
-    "showSearch": true,
-    "topSitesCount": 12,
-    "collapseTopSites": false,
-    "section.highlights.collapsed": false,
-    "section.topstories.collapsed": false,
-    "feeds.section.topstories": true,
-    "feeds.section.highlights": true
-  },
-  // Prefs listed as invalidating 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.
-  validation: ["showTopSites", "showSearch", "topSitesCount", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
-  // This means if either of these are set to their default values,
-  // prerendering can be used.
-  { oneOf: ["feeds.section.topstories", "feeds.section.highlights"] }],
-  initialSections: [{
-    enabled: true,
-    icon: "pocket",
-    id: "topstories",
-    order: 1,
-    title: { id: "header_recommended_by", values: { provider: "Pocket" } }
-  }, {
-    enabled: true,
-    id: "highlights",
-    icon: "highlights",
-    order: 2,
-    title: { id: "header_highlights" }
-  }]
-});
-module.exports = {
-  PrerenderData,
-  _PrerenderData
-};
-
-/***/ }),
-/* 15 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {const IS_NEWTAB = global.document && global.document.documentURI === "about:newtab";
 /* harmony export (immutable) */ __webpack_exports__["a"] = IS_NEWTAB;
 
 /* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
 
 /***/ }),
-/* 16 */
+/* 14 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
-/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_content_src_components_Card_Card__ = __webpack_require__(17);
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_content_src_components_Card_Card__ = __webpack_require__(15);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl__ = __webpack_require__(2);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react_intl___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_react_intl__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm__ = __webpack_require__(0);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_content_src_components_CollapsibleSection_CollapsibleSection__ = __webpack_require__(7);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_content_src_components_ComponentPerfTimer_ComponentPerfTimer__ = __webpack_require__(8);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux__ = __webpack_require__(3);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_5_react_redux___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5_react_redux__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react__ = __webpack_require__(1);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6_react__);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_content_src_components_Topics_Topics__ = __webpack_require__(18);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7_content_src_components_Topics_Topics__ = __webpack_require__(16);
 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; };
 
 
 
 
 
 
 
@@ -3635,17 +3634,17 @@ function getFormattedMessage(message) {
 
 class Section extends __WEBPACK_IMPORTED_MODULE_6_react___default.a.PureComponent {
   _dispatchImpressionStats() {
     const { props } = this;
     const maxCards = 3 * props.maxRows;
     const cards = props.rows.slice(0, maxCards);
 
     if (this.needsImpressionStats(cards)) {
-      props.dispatch(__WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm__["actionCreators"].ImpressionStats({
+      props.dispatch(__WEBPACK_IMPORTED_MODULE_2_common_Actions_jsm__["a" /* actionCreators */].ImpressionStats({
         source: props.eventSource,
         tiles: cards.map(link => ({ id: link.guid }))
       }));
       this.impressionCardGuids = cards.map(link => link.guid);
     }
   }
 
   // This sends an event when a user sees a set of new content. If content
@@ -3821,24 +3820,23 @@ class _Sections extends __WEBPACK_IMPORT
 
 
 const Sections = Object(__WEBPACK_IMPORTED_MODULE_5_react_redux__["connect"])(state => ({ Sections: state.Sections, Prefs: state.Prefs }))(_Sections);
 /* harmony export (immutable) */ __webpack_exports__["a"] = Sections;
 
 /* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
 
 /***/ }),
-/* 17 */
+/* 15 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 
 // EXTERNAL MODULE: ./system-addon/common/Actions.jsm
 var Actions = __webpack_require__(0);
-var Actions_default = /*#__PURE__*/__webpack_require__.n(Actions);
 
 // CONCATENATED MODULE: ./system-addon/content-src/components/Card/types.js
 const cardContextTypes = {
   history: {
     intlID: "type_label_visited",
     icon: "historyItem"
   },
   bookmark: {
@@ -3933,27 +3931,27 @@ class Card_Card extends external__React_
     this.setState({
       activeCard: this.props.index,
       showContextMenu: true
     });
   }
   onLinkClick(event) {
     event.preventDefault();
     const { altKey, button, ctrlKey, metaKey, shiftKey } = event;
-    this.props.dispatch(Actions["actionCreators"].SendToMain({
-      type: Actions["actionTypes"].OPEN_LINK,
+    this.props.dispatch(Actions["a" /* actionCreators */].SendToMain({
+      type: Actions["b" /* actionTypes */].OPEN_LINK,
       data: Object.assign(this.props.link, { event: { altKey, button, ctrlKey, metaKey, shiftKey } })
     }));
-    this.props.dispatch(Actions["actionCreators"].UserEvent({
+    this.props.dispatch(Actions["a" /* actionCreators */].UserEvent({
       event: "CLICK",
       source: this.props.eventSource,
       action_position: this.props.index
     }));
     if (this.props.shouldSendImpressionStats) {
-      this.props.dispatch(Actions["actionCreators"].ImpressionStats({
+      this.props.dispatch(Actions["a" /* actionCreators */].ImpressionStats({
         source: this.props.eventSource,
         click: 0,
         tiles: [{ id: this.props.link.guid, pos: this.props.index }]
       }));
     }
   }
   onMenuUpdate(showContextMenu) {
     this.setState({ showContextMenu });
@@ -4060,17 +4058,17 @@ class Card_Card extends external__React_
 
 Card_Card.defaultProps = { link: {} };
 
 const PlaceholderCard = () => external__React__default.a.createElement(Card_Card, { placeholder: true });
 /* harmony export (immutable) */ __webpack_exports__["b"] = PlaceholderCard;
 
 
 /***/ }),
-/* 18 */
+/* 16 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react_intl__ = __webpack_require__(2);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_react_intl___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_react_intl__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react__ = __webpack_require__(1);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_react___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_react__);
 
@@ -4116,36 +4114,34 @@ class Topics extends __WEBPACK_IMPORTED_
       )
     );
   }
 }
 /* harmony export (immutable) */ __webpack_exports__["a"] = Topics;
 
 
 /***/ }),
-/* 19 */
+/* 17 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__ = __webpack_require__(9);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__);
 
 
 
 const VISIBLE = "visible";
 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
 
 class DetectUserSessionStart {
   constructor(store, options = {}) {
     this._store = store;
     // Overrides for testing
     this.document = options.document || global.document;
-    this._perfService = options.perfService || __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__["perfService"];
+    this._perfService = options.perfService || __WEBPACK_IMPORTED_MODULE_1_common_PerfService_jsm__["a" /* perfService */];
     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,
    *                    because tabs are pre-loaded.
@@ -4167,18 +4163,18 @@ class DetectUserSessionStart {
    *              visibility_event_rcvd_ts time in ms from the UNIX epoch.
    */
   _sendEvent() {
     this._perfService.mark("visibility_event_rcvd_ts");
 
     try {
       let visibility_event_rcvd_ts = this._perfService.getMostRecentAbsMarkStartByName("visibility_event_rcvd_ts");
 
-      this._store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({
-        type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SAVE_SESSION_PERF_DATA,
+      this._store.dispatch(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({
+        type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SAVE_SESSION_PERF_DATA,
         data: { visibility_event_rcvd_ts }
       }));
     } catch (ex) {
       // If this failed, it's likely because the `privacy.resistFingerprinting`
       // pref is true.  We should at least not blow up.
     }
   }
 
@@ -4193,40 +4189,39 @@ class DetectUserSessionStart {
     }
   }
 }
 /* harmony export (immutable) */ __webpack_exports__["a"] = DetectUserSessionStart;
 
 /* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
 
 /***/ }),
-/* 20 */
+/* 18 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (immutable) */ __webpack_exports__["a"] = initStore;
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__ = __webpack_require__(0);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__);
-/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_redux__ = __webpack_require__(21);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_redux__ = __webpack_require__(19);
 /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_redux___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_redux__);
 /* eslint-env mozilla/frame-script */
 
 
 
 
 const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
 /* unused harmony export MERGE_STORE_ACTION */
 
 const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
 /* unused harmony export OUTGOING_MESSAGE_NAME */
 
 const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
 /* unused harmony export INCOMING_MESSAGE_NAME */
 
-const EARLY_QUEUED_ACTIONS = [__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].SAVE_SESSION_PERF_DATA, __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].PAGE_PRERENDERED];
+const EARLY_QUEUED_ACTIONS = [__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].SAVE_SESSION_PERF_DATA, __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].PAGE_PRERENDERED];
 /* unused harmony export EARLY_QUEUED_ACTIONS */
 
 
 /**
  * A higher-order function which returns a reducer that, on MERGE_STORE action,
  * will return the action.data object merged into the previous state.
  *
  * For all other actions, it merely calls mainReducer.
@@ -4250,46 +4245,46 @@ function mergeStateReducer(mainReducer) 
     return mainReducer(prevState, action);
   };
 }
 
 /**
  * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
  */
 const messageMiddleware = store => next => action => {
-  if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isSendToMain(action)) {
+  if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].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 === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].NEW_TAB_STATE_REQUEST;
+  const isRehydrationRequest = action.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].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 === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].INIT) {
-    return next(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionCreators"].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionTypes"].NEW_TAB_STATE_REQUEST }));
+  if (store._didRequestInitialState && action.type === __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].INIT) {
+    return next(__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["a" /* actionCreators */].SendToMain({ type: __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["b" /* actionTypes */].NEW_TAB_STATE_REQUEST }));
   }
 
-  if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isBroadcastToContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isSendToContent(action)) {
+  if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isBroadcastToContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isSendToContent(action) || __WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isSendToPreloaded(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);
 };
@@ -4301,17 +4296,17 @@ const rehydrationMiddleware = store => n
  * the first action from main. This is useful for those actions for main which
  * require higher reliability, i.e. the action will not be lost in the case
  * that it gets sent before the main is ready to receive it. Conversely, any
  * actions allowed early are accepted to be ignorable or re-sendable.
  */
 const queueEarlyMessageMiddleware = store => next => action => {
   if (store._receivedFromMain) {
     next(action);
-  } else if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["actionUtils"].isFromMain(action)) {
+  } else if (__WEBPACK_IMPORTED_MODULE_0_common_Actions_jsm__["c" /* actionUtils */].isFromMain(action)) {
     next(action);
     store._receivedFromMain = true;
     // Sending out all the early actions as main is ready now
     if (store._earlyActionQueue) {
       store._earlyActionQueue.forEach(next);
       store._earlyActionQueue = [];
     }
   } else if (EARLY_QUEUED_ACTIONS.includes(action.type)) {
@@ -4349,21 +4344,21 @@ function initStore(reducers, initialStat
     });
   }
 
   return store;
 }
 /* WEBPACK VAR INJECTION */}.call(__webpack_exports__, __webpack_require__(4)))
 
 /***/ }),
-/* 21 */
+/* 19 */
 /***/ (function(module, exports) {
 
 module.exports = Redux;
 
 /***/ }),
-/* 22 */
+/* 20 */
 /***/ (function(module, exports) {
 
 module.exports = ReactDOM;
 
 /***/ })
 /******/ ]);
\ No newline at end of file
--- 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>2018.01.10.1284-a48d0d39</em:version>
+    <em:version>2018.01.12.1332-b114acab</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
@@ -287,17 +287,17 @@ this.ActivityStream = class ActivityStre
     if (Services.prefs.prefHasUserValue(GEO_PREF)) {
       this.geo = Services.prefs.getStringPref(GEO_PREF);
     } else if (this.geo !== "") {
       // Watch for geo changes and use a dummy value for now
       Services.prefs.addObserver(GEO_PREF, this);
       this.geo = "";
     }
 
-    this.locale = Services.locale.getRequestedLocale();
+    this.locale = Services.locale.getAppLocaleAsLangTag();
 
     // Update the pref config of those with dynamic values
     for (const pref of PREFS_CONFIG.keys()) {
       const prefConfig = PREFS_CONFIG.get(pref);
       if (!prefConfig.getValue) {
         continue;
       }
 
--- a/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm
+++ b/browser/extensions/activity-stream/lib/ActivityStreamMessageChannel.jsm
@@ -63,16 +63,18 @@ this.ActivityStreamMessageChannel = clas
       if (!this.channel && !skipMain) {
         next(action);
         return;
       }
       if (au.isSendToContent(action)) {
         this.send(action);
       } else if (au.isBroadcastToContent(action)) {
         this.broadcast(action);
+      } else if (au.isSendToPreloaded(action)) {
+        this.sendToPreloaded(action);
       }
 
       if (!skipMain) {
         next(action);
       }
     };
   }
 
@@ -121,16 +123,50 @@ this.ActivityStreamMessageChannel = clas
       if (port.portID === id) {
         return port;
       }
     }
     return null;
   }
 
   /**
+   * sendToPreloaded - Sends an action to each preloaded browser, if any
+   *
+   * @param  {obj} action A redux action
+   */
+  sendToPreloaded(action) {
+    const preloadedBrowsers = this.getPreloadedBrowser();
+    if (preloadedBrowsers && action.data) {
+      for (let preloadedBrowser of preloadedBrowsers) {
+        try {
+          preloadedBrowser.sendAsyncMessage(this.outgoingMessageName, action);
+        } catch (e) {
+          // The preloaded page is no longer available, so just ignore.
+        }
+      }
+    }
+  }
+
+  /**
+   * getPreloadedBrowser - Retrieve the port of any preloaded browsers
+   *
+   * @return {Array|null} An array of ports belonging to the preloaded browsers, or null
+   *                      if there aren't any preloaded browsers
+   */
+  getPreloadedBrowser() {
+    let preloadedPorts = [];
+    for (let port of this.channel.messagePorts) {
+      if (port.browser.getAttribute("preloadedState") === "preloaded") {
+        preloadedPorts.push(port);
+      }
+    }
+    return preloadedPorts.length ? preloadedPorts : null;
+  }
+
+  /**
    * createChannel - Create RemotePages channel to establishing message passing
    *                 between the main process and child pages
    */
   createChannel() {
     //  Receive AboutNewTab's Remote Pages instance, if it exists, on override
     const channel = this.pageURL === ABOUT_NEW_TAB_URL && AboutNewTab.override(true);
     this.channel = channel || new RemotePages([ABOUT_HOME_URL, ABOUT_NEW_TAB_URL]);
     this.channel.addMessageListener("RemotePage:Init", this.onNewTabInit);
--- a/browser/extensions/activity-stream/lib/PlacesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/PlacesFeed.jsm
@@ -67,17 +67,17 @@ class HistoryObserver extends Observer {
    */
   onClearHistory() {
     this.dispatch({type: at.PLACES_HISTORY_CLEARED});
   }
 
   // Empty functions to make xpconnect happy
   onBeginUpdateBatch() {}
   onEndUpdateBatch() {}
-  onVisits() {}
+  onVisit() {}
   onTitleChanged() {}
   onFrecencyChanged() {}
   onManyFrecenciesChanged() {}
   onPageChanged() {}
   onDeleteVisits() {}
 }
 
 /**
--- a/browser/extensions/activity-stream/lib/SectionsManager.jsm
+++ b/browser/extensions/activity-stream/lib/SectionsManager.jsm
@@ -289,24 +289,24 @@ class SectionsFeed {
 
   onRemoveSection(event, id) {
     this.store.dispatch(ac.BroadcastToContent({type: at.SECTION_DEREGISTER, data: id}));
   }
 
   onUpdateSection(event, id, options, shouldBroadcast = false) {
     if (options) {
       const action = {type: at.SECTION_UPDATE, data: Object.assign(options, {id})};
-      this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : action);
+      this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.SendToPreloaded(action));
     }
   }
 
   onUpdateSectionCard(event, id, url, options, shouldBroadcast = false) {
     if (options) {
       const action = {type: at.SECTION_UPDATE_CARD, data: {id, url, options}};
-      this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : action);
+      this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.SendToPreloaded(action));
     }
   }
 
   onAction(action) {
     switch (action.type) {
       case at.INIT:
         SectionsManager.onceInitialized(this.init);
         break;
--- a/browser/extensions/activity-stream/lib/TelemetryFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TelemetryFeed.jsm
@@ -254,17 +254,17 @@ this.TelemetryFeed = class TelemetryFeed
    *
    * @param  {string} id The portID of the session, if a session is relevant (optional)
    * @return {obj}    A telemetry ping
    */
   createPing(portID) {
     const appInfo = this.store.getState().App;
     const ping = {
       addon_version: appInfo.version,
-      locale: Services.locale.getRequestedLocale(),
+      locale: Services.locale.getAppLocaleAsLangTag(),
       user_prefs: this.userPreferences
     };
 
     // If the ping is part of a user session, add session-related info
     if (portID) {
       const session = this.sessions.get(portID) || this.addSession(portID);
       Object.assign(ping, {session_id: session.session_id});
 
--- a/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/TopSitesFeed.jsm
@@ -152,18 +152,18 @@ this.TopSitesFeed = class TopSitesFeed {
     }
 
     const links = await this.getLinksWithDefaults();
     const newAction = {type: at.TOP_SITES_UPDATED, data: links};
     if (options.broadcast) {
       // Broadcast an update to all open content pages
       this.store.dispatch(ac.BroadcastToContent(newAction));
     } else {
-      // Don't broadcast only update the state.
-      this.store.dispatch(ac.SendToMain(newAction));
+      // Don't broadcast only update the state and update the preloaded tab.
+      this.store.dispatch(ac.SendToPreloaded(newAction));
     }
   }
 
   /**
    * Get an image for the link preferring tippy top, rich favicon, screenshots.
    */
   async _fetchIcon(link) {
     // Nothing to do if we already have a rich icon from the page
--- a/browser/extensions/activity-stream/prerendered/locales/dsb/activity-stream-strings.js
+++ b/browser/extensions/activity-stream/prerendered/locales/dsb/activity-stream-strings.js
@@ -42,17 +42,17 @@ window.gActivityStreamStrings = {
   "section_disclaimer_topstories_linktext": "Zgóńśo, kak to funkcioněrujo.",
   "section_disclaimer_topstories_buttontext": "W pórěźe, som zrozměł",
   "welcome_title": "Witajśo k nowemu rejtarkoju",
   "welcome_body": "Firefox buźo toś ten rum wužywaś, aby waše nejwažnjejše cytańske znamjenja, nastawki, wideo a rowno woglědane boki pokazał, aby mógł se lažko k nim wrośiś.",
   "welcome_label": "Wuběranje wašych nejwažnjejšych bokow",
   "time_label_less_than_minute": "<1m",
   "time_label_minute": "{number} m",
   "time_label_hour": "{number} h",
-  "time_label_day": "{number}d",
+  "time_label_day": "{number} d",
   "settings_pane_button_label": "Bok wašogo nowego rejtarka pśiměriś",
   "settings_pane_header": "Nastajenja nowego rejtarka składowaś",
   "settings_pane_body2": "Wubjeŕśo, což se na toś tom boku pokazujo.",
   "settings_pane_search_header": "Pytaś",
   "settings_pane_search_body": "Pśepytajśo web ze swójogo nowego rejtarka.",
   "settings_pane_topsites_header": "Nejcesćej woglědane sedła",
   "settings_pane_topsites_body": "Wócyńśo websedła, kótarež sćo se nejcesćej woglědał.",
   "settings_pane_topsites_options_showmore": "Dwě smužki pokazaś",
--- a/browser/extensions/activity-stream/prerendered/locales/hsb/activity-stream-strings.js
+++ b/browser/extensions/activity-stream/prerendered/locales/hsb/activity-stream-strings.js
@@ -42,17 +42,17 @@ window.gActivityStreamStrings = {
   "section_disclaimer_topstories_linktext": "Zhońće, kak to funguje.",
   "section_disclaimer_topstories_buttontext": "W porjadku, sym zrozumił",
   "welcome_title": "Witajće k nowemu rajtarkej",
   "welcome_body": "Firefox budźe tutón rum wužiwać, zo by waše najwažniše zapołožki, nastawki, wideja a runje wopytane strony pokazał, zo byšće móhł so lochko k nim wróćić.",
   "welcome_label": "Wuběranje wašich najwažnišich stronow",
   "time_label_less_than_minute": "< 1 min",
   "time_label_minute": "{number} m",
   "time_label_hour": "{number} h",
-  "time_label_day": "{number}d",
+  "time_label_day": "{number} d",
   "settings_pane_button_label": "Stronu wašeho noweho rajtarka přiměrić",
   "settings_pane_header": "Nastajenja noweho rajtarka",
   "settings_pane_body2": "Wubjerće, štož so na tutej stronje pokazuje.",
   "settings_pane_search_header": "Pytać",
   "settings_pane_search_body": "Přepytajće web ze swojeho noweho rajtarka.",
   "settings_pane_topsites_header": "Najhusćišo wopytane sydła",
   "settings_pane_topsites_body": "Wočińće websydła, kotrež sće najhusćišo wopytał.",
   "settings_pane_topsites_options_showmore": "Dwaj rjadaj pokazać",
--- a/browser/extensions/activity-stream/prerendered/locales/pt-BR/activity-stream-strings.js
+++ b/browser/extensions/activity-stream/prerendered/locales/pt-BR/activity-stream-strings.js
@@ -87,12 +87,12 @@ window.gActivityStreamStrings = {
   "topsites_form_cancel_button": "Cancelar",
   "topsites_form_url_validation": "É necessário um URL válido",
   "pocket_read_more": "Tópicos populares:",
   "pocket_read_even_more": "Ver mais histórias",
   "pocket_feedback_header": "O melhor da web, com curadoria de mais de 25 milhões de pessoas.",
   "pocket_description": "Descubra conteúdo de alta qualidade que você poderia ter perdido, com a ajuda do Pocket, agora parte da Mozilla.",
   "highlights_empty_state": "Comece a navegar e nós mostraremos aqui alguns ótimos artigos, vídeos e outras páginas que você favoritou ou visitou recentemente.",
   "topstories_empty_state": "Você já viu tudo. Volte mais tarde para mais histórias do {provider}. Não consegue esperar? Escolha um assunto popular para encontrar mais grandes histórias através da web.",
-  "manual_migration_explanation2": "Experimente o Firefox com os favoritos, histórico e senhas salvas em outros navegador.",
+  "manual_migration_explanation2": "Experimente o Firefox com os favoritos, histórico e senhas salvas em outro navegador.",
   "manual_migration_cancel_button": "Não, obrigado",
   "manual_migration_import_button": "Importar agora"
 };
--- a/browser/extensions/activity-stream/prerendered/locales/ro/activity-stream-strings.js
+++ b/browser/extensions/activity-stream/prerendered/locales/ro/activity-stream-strings.js
@@ -60,17 +60,17 @@ window.gActivityStreamStrings = {
   "settings_pane_bookmarks_body": "Marcajele memorate recent, organizate și accesibile într-un singur log.",
   "settings_pane_visit_again_header": "Vizitează din nou",
   "settings_pane_visit_again_body": "Firefox îți va arăta părți din istoricul navigării tale la care ai vrea să revii mai târziu.",
   "settings_pane_highlights_header": "Evidențieri",
   "settings_pane_highlights_body2": "Regăsește lucrurile interesante pe care le-ai vizitat sau marcat recent.",
   "settings_pane_highlights_options_bookmarks": "Marcaje",
   "settings_pane_highlights_options_visited": "Site-uri vizitate",
   "settings_pane_snippets_header": "Fragmente",
-  "settings_pane_snippets_body": "Citește actualizări scurte de la Mozilla despre Firefox, cultura internetului și meme-ul ocazional.",
+  "settings_pane_snippets_body": "Citește actualizări scurte de la Mozilla despre Firefox, cultura internetului și meme-ul ocazional aleatoriu.",
   "settings_pane_done_button": "Gata",
   "settings_pane_topstories_options_sponsored": "Arată articolele sponsorizate",
   "edit_topsites_button_text": "Editează",
   "edit_topsites_button_label": "Particularizează secțiunea site-urilor de top",
   "edit_topsites_showmore_button": "Arată mai mult",
   "edit_topsites_showless_button": "Arată mai puțin",
   "edit_topsites_done_button": "Gata",
   "edit_topsites_pin_button": "Fixează acest site",
--- a/browser/extensions/activity-stream/prerendered/locales/te/activity-stream-strings.js
+++ b/browser/extensions/activity-stream/prerendered/locales/te/activity-stream-strings.js
@@ -73,17 +73,17 @@ window.gActivityStreamStrings = {
   "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_add_header": "కొత్త మేటి సైటు",
   "topsites_form_edit_header": "టాప్ సైట్ను సవరించండి",
   "topsites_form_title_placeholder": "శీర్షికను నమోదు చేయండి",
   "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": "ప్రముఖ అంశాలు:",
--- a/browser/extensions/activity-stream/test/.eslintrc.js
+++ b/browser/extensions/activity-stream/test/.eslintrc.js
@@ -1,16 +1,14 @@
 module.exports = {
   "env": {
-    "node": true,
-    "es6": true,
     "mocha": true
   },
   "globals": {
     "assert": true,
-    "sinon": true,
-    "chai": true
+    "chai": true,
+    "sinon": true
   },
   "rules": {
     "import/no-commonjs": 2,
     "react/jsx-no-bind": 0
   }
 };
--- a/browser/extensions/activity-stream/test/functional/mochitest/browser.ini
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser.ini
@@ -1,8 +1,10 @@
 [DEFAULT]
 support-files =
   blue_page.html
   head.js
 
 [browser_as_load_location.js]
 [browser_as_render.js]
 [browser_getScreenshots.js]
+[browser_highlights_section.js]
+[browser_topsites_section.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser_highlights_section.js
@@ -0,0 +1,50 @@
+"use strict";
+
+/**
+ * Helper for setup and cleanup of Highlights section tests.
+ * @param bookmarkCount Number of bookmark higlights to add
+ * @param test The test case
+ */
+function test_highlights(bookmarkCount, test) {
+  test_newtab({
+    async before({tab}) {
+      if (bookmarkCount) {
+        await addHighlightsBookmarks(bookmarkCount);
+        // Wait for HighlightsFeed to update and display the items.
+        await ContentTask.spawn(tab.linkedBrowser, null, async () => {
+          await ContentTaskUtils.waitForCondition(() => content.document.querySelector(".card-outer:not(.placeholder)"),
+            "No highlights cards found.");
+        });
+      }
+    },
+    test,
+    async after() {
+      await clearHistoryAndBookmarks();
+    }
+  });
+}
+
+test_highlights(
+  2, // Number of highlights cards
+  function check_highlights_cards() {
+    let found = content.document.querySelectorAll(".card-outer:not(.placeholder)").length;
+    is(found, 2, "there should be 2 highlights cards");
+
+    found = content.document.querySelectorAll(".section-list .placeholder").length;
+    is(found, 1, "there should be 1 highlights placeholder");
+
+    found = content.document.querySelectorAll(".card-context-icon.icon-bookmark-added").length;
+    is(found, 2, "there should be 2 bookmark icons");
+  }
+);
+
+test_highlights(
+  1, // Number of highlights cards
+  function check_highlights_context_menu() {
+    const menuButton = content.document.querySelector(".section-list .context-menu-button");
+    // Open the menu.
+    menuButton.click();
+    const found = content.document.querySelector(".context-menu");
+    ok(found && !found.hidden, "Should find a visible context menu");
+  }
+);
new file mode 100644
--- /dev/null
+++ b/browser/extensions/activity-stream/test/functional/mochitest/browser_topsites_section.js
@@ -0,0 +1,56 @@
+"use strict";
+
+// Check TopSites edit modal and overlay show up.
+test_newtab(
+  // it should be able to click the topsites edit button to reveal the edit topsites modal and overlay.
+  function topsites_edit() {
+    const topsitesEditBtn = content.document.querySelector(".edit-topsites-button button");
+    topsitesEditBtn.click();
+
+    let found = content.document.querySelector(".edit-topsites");
+    ok(found && !found.hidden, "Should find a visible topsites edit menu");
+
+    found = content.document.querySelector(".modal-overlay");
+    ok(found && !found.hidden, "Should find a visible overlay");
+  }
+);
+
+// Test pin/unpin context menu options.
+test_newtab({
+  async before({pushPrefs}) {
+    // The pref for TopSites is empty by default.
+    await pushPrefs(["browser.newtabpage.activity-stream.default.sites", "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/"]);
+    // Toggle the feed off and on as a workaround to read the new prefs.
+    await pushPrefs(["browser.newtabpage.activity-stream.feeds.topsites", false]);
+    await pushPrefs(["browser.newtabpage.activity-stream.feeds.topsites", true]);
+  },
+  // it should pin the website when we click the first option of the topsite context menu.
+  test: async function topsites_pin_unpin() {
+    await ContentTaskUtils.waitForCondition(() => content.document.querySelector(".top-site-icon"),
+      "Topsite tippytop icon not found");
+    // There are only topsites on the page, the selector with find the first topsite menu button.
+    const topsiteContextBtn = content.document.querySelector(".context-menu-button");
+    topsiteContextBtn.click();
+
+    const contextMenu = content.document.querySelector(".context-menu");
+    ok(contextMenu && !contextMenu.hidden, "Should find a visible topsite context menu");
+
+    const pinUnpinTopsiteBtn = contextMenu.querySelector(".context-menu-item a");
+    // Pin the topsite.
+    pinUnpinTopsiteBtn.click();
+
+    // Need to wait for pin action.
+    await ContentTaskUtils.waitForCondition(() => content.document.querySelector(".icon-pin-small"),
+      "No pinned icon found");
+
+    let pinnedIcon = content.document.querySelectorAll(".icon-pin-small").length;
+    is(pinnedIcon, 1, "should find 1 pinned topsite");
+
+    // Unpin the topsite.
+    pinUnpinTopsiteBtn.click();
+
+    // Need to wait for unpin action.
+    await ContentTaskUtils.waitForCondition(() => !content.document.querySelector(".icon-pin-small"),
+      "Topsite should be unpinned");
+  }
+});
--- a/browser/extensions/activity-stream/test/functional/mochitest/head.js
+++ b/browser/extensions/activity-stream/test/functional/mochitest/head.js
@@ -1,36 +1,74 @@
 "use strict";
 
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+  "resource://testing-common/PlacesTestUtils.jsm");
+
 function popPrefs() {
   return SpecialPowers.popPrefEnv();
 }
 function pushPrefs(...prefs) {
   return SpecialPowers.pushPrefEnv({set: prefs});
 }
 
 // Activity Stream tests expect it to be enabled, and make sure to clear out any
 // preloaded browsers that might have about:newtab that we don't want to test
 const ACTIVITY_STREAM_PREF = "browser.newtabpage.activity-stream.enabled";
 pushPrefs([ACTIVITY_STREAM_PREF, true]);
 gBrowser.removePreloadedBrowser();
 
+async function clearHistoryAndBookmarks() { // eslint-disable-line no-unused-vars
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesUtils.history.clear();
+}
+
 /**
  * Helper to wait for potentially preloaded browsers to "load" where a preloaded
  * page has already loaded and won't trigger "load", and a "load"ed page might
  * not necessarily have had all its javascript/render logic executed.
  */
 async function waitForPreloaded(browser) {
   let readyState = await ContentTask.spawn(browser, {}, () => content.document.readyState);
   if (readyState !== "complete") {
     await BrowserTestUtils.browserLoaded(browser);
   }
 }
 
 /**
+ * Helper to force the HighlightsFeed to update.
+ */
+function refreshHighlightsFeed() {
+  // Toggling the pref will clear the feed cache and force a places query.
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.feeds.section.highlights", false);
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.feeds.section.highlights", true);
+}
+
+/**
+ * Helper to populate the Highlights section with bookmark cards.
+ * @param count Number of items to add.
+ */
+async function addHighlightsBookmarks(count) { // eslint-disable-line no-unused-vars
+  const bookmarks = new Array(count).fill(null).map((entry, i) => ({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    title: "foo",
+    url: `https://mozilla${i}.com/nowNew`
+  }));
+
+  for (let placeInfo of bookmarks) {
+    await PlacesUtils.bookmarks.insert(placeInfo);
+    // Bookmarks need at least one visit to show up as highlights.
+    await PlacesTestUtils.addVisits(placeInfo.url);
+  }
+
+  // Force HighlightsFeed to make a request for the new items.
+  refreshHighlightsFeed();
+}
+
+/**
  * Helper to run Activity Stream about:newtab test tasks in content.
  *
  * @param testInfo {Function|Object}
  *   {Function} This parameter will be used as if the function were called with
  *              an Object with this parameter as "test" key's value.
  *   {Object} The following keys are expected:
  *     before {Function} Optional. Runs before and returns an arg for "test"
  *     test   {Function} The test to run in the about:newtab content task taking
--- a/browser/extensions/activity-stream/test/unit/common/Actions.test.js
+++ b/browser/extensions/activity-stream/test/unit/common/Actions.test.js
@@ -2,16 +2,17 @@ import {
 
   actionCreators as ac,
   actionTypes as at,
   actionUtils as au,
   BACKGROUND_PROCESS,
   CONTENT_MESSAGE_TYPE,
   globalImportContext,
   MAIN_MESSAGE_TYPE,
+  PRELOAD_MESSAGE_TYPE,
   UI_CODE
 } from "common/Actions.jsm";
 
 describe("Actions", () => {
   it("should set globalImportContext to UI_CODE", () => {
     assert.equal(globalImportContext, UI_CODE);
   });
 });
@@ -121,16 +122,36 @@ describe("ActionCreators", () => {
         assert.isTrue(au.isBroadcastToContent(ac.BroadcastToContent({type: "FOO"})));
       });
       it("should return false if action is not BroadcastToContent", () => {
         assert.isFalse(au.isBroadcastToContent({type: "FOO"}));
         assert.isFalse(au.isBroadcastToContent(ac.SendToContent({type: "FOO"}, "foo123")));
       });
     });
   });
+  describe("SendToPreloaded", () => {
+    it("should create the right action", () => {
+      const action = {type: "FOO", data: "BAR"};
+      const newAction = ac.SendToPreloaded(action);
+      assert.deepEqual(newAction, {
+        type: "FOO",
+        data: "BAR",
+        meta: {from: MAIN_MESSAGE_TYPE, to: PRELOAD_MESSAGE_TYPE}
+      });
+    });
+  });
+  describe("isSendToPreloaded", () => {
+    it("should return true if action is SendToPreloaded", () => {
+      assert.isTrue(au.isSendToPreloaded(ac.SendToPreloaded({type: "FOO"})));
+    });
+    it("should return false if action is not SendToPreloaded", () => {
+      assert.isFalse(au.isSendToPreloaded({type: "FOO"}));
+      assert.isFalse(au.isSendToPreloaded(ac.BroadcastToContent({type: "FOO"})));
+    });
+  });
   describe("UserEvent", () => {
     it("should include the given data", () => {
       const data = {action: "foo"};
       assert.equal(ac.UserEvent(data).data, data);
     });
     it("should wrap with SendToMain", () => {
       const action = ac.UserEvent({action: "foo"});
       assert.isTrue(au.isSendToMain(action), "isSendToMain");
--- a/browser/extensions/activity-stream/test/unit/lib/ActivityStream.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/ActivityStream.test.js
@@ -166,39 +166,39 @@ describe("ActivityStream", () => {
 
       as._updateDynamicPrefs();
 
       assert.isFalse(PREFS_CONFIG.get("feeds.section.topstories").value);
     });
     it("should be false with expected geo and unexpected locale", () => {
       sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
       sandbox.stub(global.Services.prefs, "getStringPref").returns("US");
-      sandbox.stub(global.Services.locale, "getRequestedLocale").returns("no-LOCALE");
+      sandbox.stub(global.Services.locale, "getAppLocaleAsLangTag").returns("no-LOCALE");
 
       as._updateDynamicPrefs();
 
       assert.isFalse(PREFS_CONFIG.get("feeds.section.topstories").value);
     });
     it("should be true with expected geo and locale", () => {
       sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
       sandbox.stub(global.Services.prefs, "getStringPref").returns("US");
-      sandbox.stub(global.Services.locale, "getRequestedLocale").returns("en-US");
+      sandbox.stub(global.Services.locale, "getAppLocaleAsLangTag").returns("en-US");
 
       as._updateDynamicPrefs();
 
       assert.isTrue(PREFS_CONFIG.get("feeds.section.topstories").value);
     });
     it("should be false after expected geo and locale then unexpected", () => {
       sandbox.stub(global.Services.prefs, "prefHasUserValue").returns(true);
       sandbox.stub(global.Services.prefs, "getStringPref")
         .onFirstCall()
         .returns("US")
         .onSecondCall()
         .returns("NOGEO");
-      sandbox.stub(global.Services.locale, "getRequestedLocale").returns("en-US");
+      sandbox.stub(global.Services.locale, "getAppLocaleAsLangTag").returns("en-US");
 
       as._updateDynamicPrefs();
       as._updateDynamicPrefs();
 
       assert.isFalse(PREFS_CONFIG.get("feeds.section.topstories").value);
     });
   });
   describe("_updateDynamicPrefs topstories delayed default value", () => {
@@ -219,17 +219,17 @@ describe("ActivityStream", () => {
 
       as._updateDynamicPrefs();
       clock.tick(1);
 
       assert.isFalse(PREFS_CONFIG.get("feeds.section.topstories").value);
     });
     it("should set true with expected geo and locale", () => {
       sandbox.stub(global.Services.prefs, "getStringPref").returns("US");
-      sandbox.stub(global.Services.locale, "getRequestedLocale").returns("en-US");
+      sandbox.stub(global.Services.locale, "getAppLocaleAsLangTag").returns("en-US");
 
       as._updateDynamicPrefs();
       clock.tick(1);
 
       assert.isTrue(PREFS_CONFIG.get("feeds.section.topstories").value);
     });
   });
   describe("telemetry reporting on init failure", () => {
--- a/browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/ActivityStreamMessageChannel.test.js
@@ -153,16 +153,55 @@ describe("ActivityStreamMessageChannel",
       });
       it("should return null if the target doesn't exist", () => {
         const t = {portID: "foo"};
         mm.createChannel();
         mm.channel.messagePorts.push(t);
         assert.equal(mm.getTargetById("bar"), null);
       });
     });
+    describe("#getPreloadedBrowser", () => {
+      it("should get a preloaded browser if it exists", () => {
+        const port = {
+          browser: {
+            getAttribute() {
+              return "preloaded";
+            }
+          }
+        };
+        mm.createChannel();
+        mm.channel.messagePorts.push(port);
+        assert.equal(mm.getPreloadedBrowser()[0], port);
+      });
+      it("should get all the preloaded browsers across windows if they exist", () => {
+        const port = {
+          browser: {
+            getAttribute() {
+              return "preloaded";
+            }
+          }
+        };
+        mm.createChannel();
+        mm.channel.messagePorts.push(port);
+        mm.channel.messagePorts.push(port);
+        assert.equal(mm.getPreloadedBrowser().length, 2);
+      });
+      it("should return null if there is no preloaded browser", () => {
+        const port = {
+          browser: {
+            getAttribute() {
+              return "consumed";
+            }
+          }
+        };
+        mm.createChannel();
+        mm.channel.messagePorts.push(port);
+        assert.equal(mm.getPreloadedBrowser(), null);
+      });
+    });
     describe("#onNewTabInit", () => {
       it("should dispatch a NEW_TAB_INIT action", () => {
         const t = {portID: "foo", url: "about:monkeys"};
         sinon.stub(mm, "onActionFromContent");
 
         mm.onNewTabInit({target: t});
 
         assert.calledWith(mm.onActionFromContent, {
@@ -236,16 +275,63 @@ describe("ActivityStreamMessageChannel",
     describe("#broadcast", () => {
       it("should send a message on the channel", () => {
         mm.createChannel();
         const action = ac.BroadcastToContent({type: "HELLO"});
         mm.broadcast(action);
         assert.calledWith(mm.channel.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
       });
     });
+    describe("#preloaded browser", () => {
+      it("should send the message to the preloaded browser if there's data and a preloaded browser exists", () => {
+        const port = {
+          browser: {
+            getAttribute() {
+              return "preloaded";
+            }
+          },
+          sendAsyncMessage: sinon.spy()
+        };
+        mm.createChannel();
+        mm.channel.messagePorts.push(port);
+        const action = ac.SendToPreloaded({type: "HELLO", data: 10});
+        mm.sendToPreloaded(action);
+        assert.calledWith(port.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
+      });
+      it("should send the message to all the preloaded browsers if there's data and they exist", () => {
+        const port = {
+          browser: {
+            getAttribute() {
+              return "preloaded";
+            }
+          },
+          sendAsyncMessage: sinon.spy()
+        };
+        mm.createChannel();
+        mm.channel.messagePorts.push(port);
+        mm.channel.messagePorts.push(port);
+        mm.sendToPreloaded(ac.SendToPreloaded({type: "HELLO", data: 10}));
+        assert.calledTwice(port.sendAsyncMessage);
+      });
+      it("should not send the message to the preloaded browser if there's no data and a preloaded browser does not exists", () => {
+        const port = {
+          browser: {
+            getAttribute() {
+              return "consumed";
+            }
+          },
+          sendAsyncMessage: sinon.spy()
+        };
+        mm.createChannel();
+        mm.channel.messagePorts.push(port);
+        const action = ac.SendToPreloaded({type: "HELLO"});
+        mm.sendToPreloaded(action);
+        assert.notCalled(port.sendAsyncMessage);
+      });
+    });
   });
   describe("Handling actions", () => {
     describe("#onActionFromContent", () => {
       beforeEach(() => mm.onActionFromContent({type: "FOO"}, "foo"));
       it("should dispatch a SendToMain action", () => {
         assert.calledOnce(dispatch);
         const action = dispatch.firstCall.args[0];
         assert.equal(action.type, "FOO", "action.type");
@@ -288,22 +374,33 @@ describe("ActivityStreamMessageChannel",
         sinon.stub(mm, "broadcast");
         const action = ac.BroadcastToContent({type: "FOO"});
 
         mm.createChannel();
         store.dispatch(action);
 
         assert.calledWith(mm.broadcast, action);
       });
+      it("should call .sendToPreloaded if the action is SendToPreloaded", () => {
+        sinon.stub(mm, "sendToPreloaded");
+        const action = ac.SendToPreloaded({type: "FOO"});
+
+        mm.createChannel();
+        store.dispatch(action);
+
+        assert.calledWith(mm.sendToPreloaded, action);
+      });
       it("should dispatch other actions normally", () => {
         sinon.stub(mm, "send");
         sinon.stub(mm, "broadcast");
+        sinon.stub(mm, "sendToPreloaded");
 
         mm.createChannel();
         store.dispatch({type: "ADD", data: 1});
 
         assert.equal(store.getState(), 1);
         assert.notCalled(mm.send);
         assert.notCalled(mm.broadcast);
+        assert.notCalled(mm.sendToPreloaded);
       });
     });
   });
 });
--- a/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/SectionsManager.test.js
@@ -1,10 +1,10 @@
 "use strict";
-import {CONTENT_MESSAGE_TYPE, MAIN_MESSAGE_TYPE} from "common/Actions.jsm";
+import {CONTENT_MESSAGE_TYPE, MAIN_MESSAGE_TYPE, PRELOAD_MESSAGE_TYPE} from "common/Actions.jsm";
 import {EventEmitter, GlobalOverrider} from "test/unit/utils";
 import {SectionsFeed, SectionsManager} from "lib/SectionsManager.jsm";
 
 const FAKE_ID = "FAKE_ID";
 const FAKE_OPTIONS = {icon: "FAKE_ICON", title: "FAKE_TITLE"};
 const FAKE_ROWS = [{url: "1.example.com"}, {url: "2.example.com"}, {"url": "3.example.com"}];
 const FAKE_URL = "2.example.com";
 const FAKE_CARD_OPTIONS = {title: "Some fake title"};
@@ -349,18 +349,19 @@ describe("SectionsFeed", () => {
       feed.onUpdateSection(null, FAKE_ID, null);
       assert.notCalled(feed.store.dispatch);
     });
     it("should dispatch a SECTION_UPDATE action with the correct data", () => {
       feed.onUpdateSection(null, FAKE_ID, {rows: FAKE_ROWS});
       const action = feed.store.dispatch.firstCall.args[0];
       assert.equal(action.type, "SECTION_UPDATE");
       assert.deepEqual(action.data, {id: FAKE_ID, rows: FAKE_ROWS});
-      // Should be not broadcast by default, so meta should not exist
-      assert.notOk(action.meta);
+      // Should be not broadcast by default, but should update the preloaded tab, so check meta
+      assert.equal(action.meta.from, MAIN_MESSAGE_TYPE);
+      assert.equal(action.meta.to, PRELOAD_MESSAGE_TYPE);
     });
     it("should broadcast the action only if shouldBroadcast is true", () => {
       feed.onUpdateSection(null, FAKE_ID, {rows: FAKE_ROWS}, true);
       const action = feed.store.dispatch.firstCall.args[0];
       // Should be broadcast
       assert.equal(action.meta.from, MAIN_MESSAGE_TYPE);
       assert.equal(action.meta.to, CONTENT_MESSAGE_TYPE);
     });
@@ -370,18 +371,19 @@ describe("SectionsFeed", () => {
       feed.onUpdateSectionCard(null, FAKE_ID, FAKE_URL, null);
       assert.notCalled(feed.store.dispatch);
     });
     it("should dispatch a SECTION_UPDATE_CARD action with the correct data", () => {
       feed.onUpdateSectionCard(null, FAKE_ID, FAKE_URL, FAKE_CARD_OPTIONS);
       const action = feed.store.dispatch.firstCall.args[0];
       assert.equal(action.type, "SECTION_UPDATE_CARD");
       assert.deepEqual(action.data, {id: FAKE_ID, url: FAKE_URL, options: FAKE_CARD_OPTIONS});
-      // Should be not broadcast by default, so meta should not exist
-      assert.notOk(action.meta);
+      // Should be not broadcast by default, but should update the preloaded tab, so check meta
+      assert.equal(action.meta.from, MAIN_MESSAGE_TYPE);
+      assert.equal(action.meta.to, PRELOAD_MESSAGE_TYPE);
     });
     it("should broadcast the action only if shouldBroadcast is true", () => {
       feed.onUpdateSectionCard(null, FAKE_ID, FAKE_URL, FAKE_CARD_OPTIONS, true);
       const action = feed.store.dispatch.firstCall.args[0];
       // Should be broadcast
       assert.equal(action.meta.from, MAIN_MESSAGE_TYPE);
       assert.equal(action.meta.to, CONTENT_MESSAGE_TYPE);
     });
--- a/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopSitesFeed.test.js
@@ -419,22 +419,22 @@ describe("Top Sites Feed", () => {
       assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, reference);
     });
     it("should handle empty slots in the resulting top sites array", async () => {
       links = [FAKE_LINKS[0]];
       fakeNewTabUtils.pinnedLinks.links = [null, null, FAKE_LINKS[1], null, null, null, null, null, FAKE_LINKS[2]];
       await feed.refresh({broadcast: true});
       assert.calledOnce(feed.store.dispatch);
     });
-    it("should dispatch sendToMain when broadcast is false", async () => {
+    it("should dispatch SendToPreloaded when broadcast is false", async () => {
       sandbox.stub(feed, "getLinksWithDefaults").returns([]);
       await feed.refresh({broadcast: false});
 
       assert.calledOnce(feed.store.dispatch);
-      assert.calledWithExactly(feed.store.dispatch, ac.SendToMain({
+      assert.calledWithExactly(feed.store.dispatch, ac.SendToPreloaded({
         type: at.TOP_SITES_UPDATED,
         data: []
       }));
     });
   });
   describe("#_fetchIcon", () => {
     it("should reuse screenshot on the link", () => {
       const link = {screenshot: "reuse.png"};
--- a/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/TopStoriesFeed.test.js
@@ -27,17 +27,16 @@ describe("Top Stories Feed", () => {
     "provider_icon": "provider-icon",
     "provider_description": "provider_desc"
   };
 
   beforeEach(() => {
     FakePrefs.prototype.prefs.apiKeyPref = "test-api-key";
 
     globals = new GlobalOverrider();
-    globals.set("Services", {locale: {getRequestedLocale: () => "en-CA"}, obs: {addObserver: () => {}, removeObserver: () => {}}});
     globals.set("PlacesUtils", {history: {}});
     clock = sinon.useFakeTimers();
     shortURLStub = sinon.stub().callsFake(site => site.url);
     sectionsManagerStub = {
       onceInitialized: sinon.stub().callsFake(callback => callback()),
       enableSection: sinon.spy(),
       disableSection: sinon.spy(),
       updateSection: sinon.spy(),
@@ -124,18 +123,16 @@ describe("Top Stories Feed", () => {
     it("should report error for invalid configuration", () => {
       globals.sandbox.spy(global.Components.utils, "reportError");
       sectionsManagerStub.sections.set("topstories", {options: {api_key_pref: "invalid"}});
       instance.init();
 
       assert.called(Components.utils.reportError);
     });
     it("should report error for missing api key", () => {
-      let fakeServices = {prefs: {getCharPref: sinon.spy()}, locale: {getRequestedLocale: sinon.spy()}};
-      globals.set("Services", fakeServices);
       globals.sandbox.spy(global.Components.utils, "reportError");
       sectionsManagerStub.sections.set("topstories", {
         options: {
           "stories_endpoint": "https://somedomain.org/stories?key=$apiKey",
           "topics_endpoint": "https://somedomain.org/topics?key=$apiKey"
         }
       });
       instance.init();
--- a/browser/extensions/activity-stream/test/unit/lib/UserDomainAffinityProvider.test.js
+++ b/browser/extensions/activity-stream/test/unit/lib/UserDomainAffinityProvider.test.js
@@ -31,17 +31,17 @@ const PARAMETER_SETS = {
 
 describe("User Domain Affinity Provider", () => {
   let UserDomainAffinityProvider;
   let instance;
   let globals;
 
   beforeEach(() => {
     globals = new GlobalOverrider();
-    globals.set("Services", {locale: {getRequestedLocale: () => "en-CA"}, io: {newURI: u => ({host: "www.somedomain.org"})}});
+    globals.set("Services", {io: {newURI: u => ({host: "www.somedomain.org"})}});
     globals.set("PlacesUtils", {
       history: {
         getNewQuery: () => ({"TIME_RELATIVE_NOW": 1}),
         getNewQueryOptions: () => ({}),
         executeQuery: () => ({root: {childCount: 1, getChild: index => ({uri: "www.somedomain.org", accessCount: 1})}})
       }
     });
     global.Components.classes["@mozilla.org/browser/nav-history-service;1"] = {
--- a/browser/extensions/activity-stream/test/unit/unit-entry.js
+++ b/browser/extensions/activity-stream/test/unit/unit-entry.js
@@ -43,20 +43,18 @@ overrider.set({
   ContentSearchUIController: function() {}, // NB: This is a function/constructor
   dump() {},
   fetch() {},
   // eslint-disable-next-line object-shorthand
   Image: function() {}, // NB: This is a function/constructor
   Preferences: FakePrefs,
   Services: {
     locale: {
+      getAppLocaleAsLangTag() { return "en-US"; },
       getAppLocalesAsLangTags() {},
-      getRequestedLocale() {
-        return "en-US";
-      },
       negotiateLanguages() {}
     },
     urlFormatter: {formatURL: str => str},
     mm: {
       addMessageListener: (msg, cb) => cb(),
       removeMessageListener() {}
     },
     appShell: {hiddenDOMWindow: {performance: new FakePerformance()}},
--- a/browser/modules/PingCentre.jsm
+++ b/browser/modules/PingCentre.jsm
@@ -16,16 +16,35 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 const PREF_BRANCH = "browser.ping-centre.";
 
 const TELEMETRY_PREF = `${PREF_BRANCH}telemetry`;
 const LOGGING_PREF = `${PREF_BRANCH}log`;
 const PRODUCTION_ENDPOINT_PREF = `${PREF_BRANCH}production.endpoint`;
 
 const FHR_UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
+const BROWSER_SEARCH_REGION_PREF = "browser.search.region";
+
+// Only report region for following regions, to ensure that users in countries
+// with small user population (less than 10000) cannot be uniquely identified.
+// See bug 1421422 for more details.
+const REGION_WHITELIST = new Set([
+  "AE", "AF", "AL", "AM", "AR", "AT", "AU", "AZ", "BA", "BD", "BE", "BF",
+  "BG", "BJ", "BO", "BR", "BY", "CA", "CH", "CI", "CL", "CM", "CN", "CO",
+  "CR", "CU", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", "EG", "ES",
+  "ET", "FI", "FR", "GB", "GE", "GH", "GP", "GR", "GT", "HK", "HN", "HR",
+  "HU", "ID", "IE", "IL", "IN", "IQ", "IR", "IS", "IT", "JM", "JO", "JP",
+  "KE", "KH", "KR", "KW", "KZ", "LB", "LK", "LT", "LU", "LV", "LY", "MA",
+  "MD", "ME", "MG", "MK", "ML", "MM", "MN", "MQ", "MT", "MU", "MX", "MY",
+  "MZ", "NC", "NG", "NI", "NL", "NO", "NP", "NZ", "OM", "PA", "PE", "PH",
+  "PK", "PL", "PR", "PS", "PT", "PY", "QA", "RE", "RO", "RS", "RU", "RW",
+  "SA", "SD", "SE", "SG", "SI", "SK", "SN", "SV", "SY", "TG", "TH", "TN",
+  "TR", "TT", "TW", "TZ", "UA", "UG", "US", "UY", "UZ", "VE", "VN", "ZA",
+  "ZM", "ZW"
+]);
 
 /**
  * Observe various notifications and send them to a telemetry endpoint.
  *
  * @param {Object} options
  * @param {string} options.topic - a unique ID for users of PingCentre to distinguish
  *                  their data on the server side.
  * @param {string} options.overrideEndpointPref - optional pref for URL where the POST is sent.
@@ -97,36 +116,56 @@ class PingCentre {
         continue;
       }
       let expString = `${experimentID}:${activeExperiments[experimentID].branch}`;
       experimentsString = experimentsString.concat(`${expString};`);
     }
     return experimentsString;
   }
 
+  _getRegion() {
+    let region = "UNSET";
+
+    if (Services.prefs.prefHasUserValue(BROWSER_SEARCH_REGION_PREF)) {
+      region = Services.prefs.getStringPref(BROWSER_SEARCH_REGION_PREF);
+      if (region === "") {
+        region = "EMPTY";
+      } else if (!REGION_WHITELIST.has(region)) {
+        region = "OTHER";
+      }
+    }
+    return region;
+  }
+
   async sendPing(data, options) {
     let filter = options && options.filter;
     let experiments = TelemetryEnvironment.getActiveExperiments();
     let experimentsString = this._createExperimentsString(experiments, filter);
     if (!this.enabled) {
       return Promise.resolve();
     }
 
     let clientID = data.client_id || await this.telemetryClientId;
     let locale = data.locale || Services.locale.getAppLocalesAsLangTags().pop();
+    let profileCreationDate = TelemetryEnvironment.currentEnvironment.profile.resetDate ||
+      TelemetryEnvironment.currentEnvironment.profile.creationDate;
     const payload = Object.assign({
       locale,
       topic: this._topic,
       client_id: clientID,
       version: AppConstants.MOZ_APP_VERSION,
       release_channel: AppConstants.MOZ_UPDATE_CHANNEL
     }, data);
     if (experimentsString) {
       payload.shield_id = experimentsString;
     }
+    if (profileCreationDate) {
+      payload.profile_creation_date = profileCreationDate;
+    }
+    payload.region = this._getRegion();
 
     if (this.logging) {
       // performance related pings cause a lot of logging, so we mute them
       if (data.action !== "activity_stream_performance") {
         Services.console.logStringMessage(`TELEMETRY PING: ${JSON.stringify(payload)}\n`);
       }
     }