Bug 1430272 - Add preloaded updating, whitelisted regions and bug fixes to Activity Stream. r=dmose
authorEd Lee <edilee@mozilla.com>
Fri, 12 Jan 2018 14:12:57 -0800
changeset 399224 0d163e2c4792972b107a4bae282dec16eb410114
parent 399223 4259ed6ea40f6e9af6d5d4dc210685f7ea313768
child 399225 b9bfb3fc05dbc97b23d52ac8699bbafb550e8d87
push id33250
push usershindli@mozilla.com
push dateSun, 14 Jan 2018 09:52:43 +0000
treeherdermozilla-central@f40f5ac940c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdmose
bugs1430272
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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`);
       }
     }