Merge mozilla-central to autoland. a=merge CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Mon, 04 Mar 2019 23:52:28 +0200
changeset 520162 33a6f98be59c81ef5ebf53c3853a76b340ede88c
parent 520161 faa8822a034d76e920b9c4670795fd6b100836f4 (diff)
parent 520136 8602628e7edaecadea855a64179b329da6ff1f20 (current diff)
child 520163 34d5cbb640e33ad6b06af239788470118156764b
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
--- a/browser/components/extensions/test/browser/browser_ext_incognito_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_incognito_popup.js
@@ -1,14 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(async function testIncognitoPopup() {
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       "permissions": ["tabs"],
       "browser_action": {
         "default_popup": "popup.html",
       },
       "page_action": {
         "default_popup": "popup.html",
       },
--- a/browser/components/extensions/test/browser/browser_ext_incognito_views.js
+++ b/browser/components/extensions/test/browser/browser_ext_incognito_views.js
@@ -2,16 +2,17 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(async function testIncognitoViews() {
   // Make sure the mouse isn't hovering over the browserAction widget.
   EventUtils.synthesizeMouseAtCenter(gURLBar.textbox, {type: "mouseover"}, window);
 
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       "permissions": ["tabs"],
       "browser_action": {
         "default_popup": "popup.html",
       },
     },
 
     background: async function() {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
@@ -26,16 +26,17 @@ add_task(async function() {
     {privateTab: true, cookieStoreId: null, success: true, expectedCookieStoreId: "firefox-private"},
     {privateTab: true, cookieStoreId: "firefox-private", success: true, expectedCookieStoreId: "firefox-private"},
     {privateTab: true, cookieStoreId: "firefox-default", failure: "privateToDefault"},
     {privateTab: true, cookieStoreId: "firefox-container-1", failure: "privateToDefault"},
     {privateTab: true, cookieStoreId: "wow", failure: "illegal"},
   ];
 
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       "permissions": ["tabs", "cookies"],
     },
 
     background: function() {
       function testTab(data, tab) {
         browser.test.assertTrue(data.success, "we want a success");
         browser.test.assertTrue(!!tab, "we have a tab");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId_private.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId_private.js
@@ -7,16 +7,17 @@ add_task(async function perma_private_br
   await SpecialPowers.pushPrefEnv({"set": [
     ["privacy.userContext.enabled", true],
   ]});
 
   Assert.equal(Services.prefs.getBoolPref("browser.privatebrowsing.autostart"),
                true, "Permanent private browsing is enabled");
 
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       "permissions": ["tabs", "cookies"],
     },
     async background() {
       let win = await browser.windows.create({});
       browser.test.assertTrue(win.incognito, "New window should be private when perma-PBM is enabled.");
       await browser.test.assertRejects(
         browser.tabs.create({cookieStoreId: "firefox-container-1", windowId: win.id}),
--- a/browser/components/extensions/test/browser/browser_ext_tabs_create_url.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_create_url.js
@@ -78,16 +78,17 @@ add_task(async function test_urlbar_focu
   extension.sendMessage("remove", tab2.id);
   await extension.awaitMessage("result");
 
   await extension.unload();
 });
 
 add_task(async function default_url() {
   const extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       permissions: ["tabs"],
     },
     background() {
       function promiseNonBlankTab() {
         return new Promise(resolve => {
           browser.tabs.onUpdated.addListener(function listener(tabId, changeInfo, tab) {
             if (changeInfo.status === "complete" && tab.url !== "about:blank") {
--- a/browser/components/extensions/test/browser/browser_ext_windows_create_cookieStoreId.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_cookieStoreId.js
@@ -23,16 +23,17 @@ add_task(async function no_cookies_permi
 });
 
 add_task(async function invalid_cookieStoreId() {
   await SpecialPowers.pushPrefEnv({"set": [
     ["privacy.userContext.enabled", true],
   ]});
 
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       permissions: ["cookies"],
     },
     async background() {
       await browser.test.assertRejects(
         browser.windows.create({cookieStoreId: "not-firefox-container-1"}),
         /Illegal cookieStoreId/,
         "cookieStoreId must be valid");
@@ -59,16 +60,17 @@ add_task(async function invalid_cookieSt
   await extension.awaitMessage("done");
   await extension.unload();
 });
 
 add_task(async function perma_private_browsing_mode() {
   await SpecialPowers.pushPrefEnv({set: [["browser.privatebrowsing.autostart", true]]});
 
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       "permissions": ["tabs", "cookies"],
     },
     async background() {
       await browser.test.assertRejects(
         browser.windows.create({cookieStoreId: "firefox-container-1"}),
         /Contextual identities are unavailable in permanent private browsing mode/,
         "cookieStoreId cannot be a container tab ID in perma-private browsing mode");
@@ -235,16 +237,17 @@ add_task(async function valid_cookieStor
 });
 
 add_task(async function cookieStoreId_and_tabId() {
   await SpecialPowers.pushPrefEnv({"set": [
     ["privacy.userContext.enabled", true],
   ]});
 
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       permissions: ["cookies"],
     },
     async background() {
       for (let cookieStoreId of ["firefox-default", "firefox-container-1"]) {
         let {id: normalTabId} = await browser.tabs.create({cookieStoreId});
 
         await browser.test.assertRejects(
--- a/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
@@ -134,16 +134,17 @@ add_task(async function testWindowCreate
       browser.test.notifyPass("window-create");
     } catch (e) {
       browser.test.fail(`${e} :: ${e.stack}`);
       browser.test.notifyFail("window-create");
     }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
     manifest: {
       "permissions": ["tabs"],
     },
 
     background,
   });
 
   await extension.startup();
--- a/browser/components/newtab/.eslintrc.js
+++ b/browser/components/newtab/.eslintrc.js
@@ -10,17 +10,18 @@ module.exports = {
   },
   "env": {
     "node": true
   },
   "plugins": [
     "import", // require("eslint-plugin-import")
     "json", // require("eslint-plugin-json")
     "promise", // require("eslint-plugin-promise")
-    "react" // require("eslint-plugin-react")
+    "react", // require("eslint-plugin-react")
+    "react-hooks" // require("react-hooks")
   ],
   "settings": {
     "react": {
       "version": "16.2.0"
     }
   },
   "extends": [
     "eslint:recommended",
@@ -39,16 +40,18 @@ module.exports = {
     "env": {
       "node": false
     },
     "rules": {
       "no-implicit-globals": 0
     }
   }],
   "rules": {
+    "react-hooks/rules-of-hooks": 2,
+
     "promise/catch-or-return": 2,
     "promise/param-names": 2,
 
     "react/jsx-boolean-value": [2, "always"],
     "react/jsx-closing-bracket-location": [2, "after-props"],
     "react/jsx-curly-spacing": [2, "never"],
     "react/jsx-equals-spacing": [2, "never"],
     "react/jsx-key": 2,
new file mode 100755
--- /dev/null
+++ b/browser/components/newtab/bin/vendor-react.js
@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+
+/* eslint-disable no-console */
+
+const {cp, set} = require("shelljs");
+const path = require("path");
+
+const filesToVendor = {
+  // XXX currently these two licenses are identical.  Perhaps we should check
+  // in case that changes at some point in the future.
+  "react/LICENSE": "REACT_AND_REACT_DOM_LICENSE",
+  "react/umd/react.production.min.js": "react.js",
+  "react/umd/react.development.js": "react-dev.js",
+  "react-dom/umd/react-dom.production.min.js": "react-dom.js",
+  "react-dom/umd/react-dom.development.js": "react-dom-dev.js",
+};
+
+set("-v"); // Echo all the copy commands so the user can see what's going on
+for (let srcPath of Object.keys(filesToVendor)) {
+  cp(path.join("node_modules", srcPath),
+    path.join("vendor", filesToVendor[srcPath]));
+}
+
+console.log(`
+Check to see if any license files have changed, and, if so, be sure to update
+https://searchfox.org/mozilla-central/source/toolkit/content/license.html`);
--- a/browser/components/newtab/common/Actions.jsm
+++ b/browser/components/newtab/common/Actions.jsm
@@ -145,16 +145,17 @@ const ASRouterActions = {};
 for (const type of [
   "INSTALL_ADDON_FROM_URL",
   "OPEN_APPLICATIONS_MENU",
   "OPEN_PRIVATE_BROWSER_WINDOW",
   "OPEN_URL",
   "OPEN_ABOUT_PAGE",
   "OPEN_PREFERENCES_PAGE",
   "SHOW_FIREFOX_ACCOUNTS",
+  "PIN_CURRENT_TAB",
 ]) {
   ASRouterActions[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 ? {...action.meta} : {};
--- a/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md
+++ b/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md
@@ -25,16 +25,17 @@ Please note that some targeting attribut
 * [providerCohorts](#providercohorts)
 * [region](#region)
 * [searchEngines](#searchengines)
 * [sync](#sync)
 * [topFrecentSites](#topfrecentsites)
 * [totalBookmarksCount](#totalbookmarkscount)
 * [usesFirefoxSync](#usesfirefoxsync)
 * [xpinstallEnabled](#xpinstallEnabled)
+* [hasPinnedTabs](#haspinnedtabs)
 
 ## Detailed usage
 
 ### `addonsInfo`
 Provides information about the add-ons the user has installed.
 
 Note that the `name`, `userDisabled`, and `installDate` is only available if `isFullData` is `true` (this is usually not the case right at start-up).
 
@@ -437,8 +438,18 @@ declare const usesFirefoxSync: boolean;
 
 Pref used by system administrators to disallow add-ons from installed altogether.
 
 #### Definition
 
 ```ts
 declare const xpinstallEnabled: boolean;
 ```
+
+### `hasPinnedTabs`
+
+Does the user have any pinned tabs in any windows.
+
+#### Definition
+
+```ts
+declare const hasPinnedTabs: boolean;
+```
--- a/browser/components/newtab/content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/CFR/templates/ExtensionDoorhanger.schema.json
@@ -294,10 +294,10 @@
               }
             }
           }
         }
       }
     }
   },
   "additionalProperties": false,
-  "required": ["bucket_id", "notification_text", "heading_text", "addon", "text", "buttons"]
+  "required": ["bucket_id", "notification_text", "heading_text", "text", "buttons"]
 }
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
@@ -10,23 +10,23 @@ export class CardGrid extends React.Pure
       return (
         <div />
       );
     }
 
     let cards = data.recommendations.slice(0, this.props.items).map((rec, index) => (
       <DSCard
         key={`dscard-${index}`}
+        pos={rec.pos}
         campaignId={rec.campaign_id}
         image_src={rec.image_src}
         title={rec.title}
         excerpt={rec.excerpt}
         url={rec.url}
         id={rec.id}
-        index={index}
         type={this.props.type}
         context={rec.context}
         dispatch={this.props.dispatch}
         source={rec.domain} />
     ));
 
     let divisibility = ``;
 
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -10,23 +10,23 @@ export class DSCard extends React.PureCo
     this.onLinkClick = this.onLinkClick.bind(this);
   }
 
   onLinkClick(event) {
     if (this.props.dispatch) {
       this.props.dispatch(ac.UserEvent({
         event: "CLICK",
         source: this.props.type.toUpperCase(),
-        action_position: this.props.index,
+        action_position: this.props.pos,
       }));
 
       this.props.dispatch(ac.ImpressionStats({
         source: this.props.type.toUpperCase(),
         click: 0,
-        tiles: [{id: this.props.id, pos: this.props.index}],
+        tiles: [{id: this.props.id, pos: this.props.pos}],
       }));
     }
   }
 
   render() {
     return (
       <SafeAnchor url={this.props.url} className="ds-card" onLinkClick={this.onLinkClick}>
         <div className="img-wrapper">
@@ -44,15 +44,15 @@ export class DSCard extends React.PureCo
                 <br />
               </span>
             )}
             <span className="source">{this.props.source}</span>
           </p>
         </div>
         <ImpressionStats
           campaignId={this.props.campaignId}
-          rows={[{id: this.props.id}]}
+          rows={[{id: this.props.id, pos: this.props.pos}]}
           dispatch={this.props.dispatch}
           source={this.props.type} />
       </SafeAnchor>
     );
   }
 }
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
@@ -11,23 +11,23 @@ export class Hero extends React.PureComp
     this.onLinkClick = this.onLinkClick.bind(this);
   }
 
   onLinkClick(event) {
     if (this.props.dispatch) {
       this.props.dispatch(ac.UserEvent({
         event: "CLICK",
         source: this.props.type.toUpperCase(),
-        action_position: 0,
+        action_position: this.heroRec.pos,
       }));
 
       this.props.dispatch(ac.ImpressionStats({
         source: this.props.type.toUpperCase(),
         click: 0,
-        tiles: [{id: this.heroRec.id, pos: 0}],
+        tiles: [{id: this.heroRec.id, pos: this.heroRec.pos}],
       }));
     }
   }
 
   render() {
     const {data} = this.props;
 
     // Handle a render before feed has been fetched by displaying nothing
@@ -35,26 +35,25 @@ export class Hero extends React.PureComp
       return (
         <div />
       );
     }
 
     let [heroRec, ...otherRecs] = data.recommendations.slice(0, this.props.items);
     this.heroRec = heroRec;
 
-    // Note that `{index + 1}` is necessary below for telemetry since we treat heroRec as index 0.
     let cards = otherRecs.map((rec, index) => (
       <DSCard
         campaignId={rec.campaign_id}
         key={`dscard-${index}`}
         image_src={rec.image_src}
         title={rec.title}
         url={rec.url}
         id={rec.id}
-        index={index + 1}
+        pos={rec.pos}
         type={this.props.type}
         dispatch={this.props.dispatch}
         context={rec.context}
         source={rec.domain} />
     ));
 
     let list = (
       <List
@@ -82,17 +81,17 @@ export class Hero extends React.PureComp
               {heroRec.context ? (
                 <p className="context">{heroRec.context}</p>
               ) : (
                 <p className="source">{heroRec.domain}</p>
               )}
             </div>
             <ImpressionStats
               campaignId={heroRec.campaignId}
-              rows={[{id: heroRec.id}]}
+              rows={[{id: heroRec.id, pos: heroRec.pos}]}
               dispatch={this.props.dispatch}
               source={this.props.type} />
           </SafeAnchor>
           <div className={`${this.props.subComponentType}`}>
             { this.props.subComponentType === `cards` ? cards : list }
           </div>
         </div>
       </div>
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
@@ -15,23 +15,23 @@ export class ListItem extends React.Pure
     this.onLinkClick = this.onLinkClick.bind(this);
   }
 
   onLinkClick(event) {
     if (this.props.dispatch) {
       this.props.dispatch(ac.UserEvent({
         event: "CLICK",
         source: this.props.type.toUpperCase(),
-        action_position: this.props.index,
+        action_position: this.props.pos,
       }));
 
       this.props.dispatch(ac.ImpressionStats({
         source: this.props.type.toUpperCase(),
         click: 0,
-        tiles: [{id: this.props.id, pos: this.props.index}],
+        tiles: [{id: this.props.id, pos: this.props.pos}],
       }));
     }
   }
 
   render() {
     return (
       <li className="ds-list-item">
         <SafeAnchor url={this.props.url} className="ds-list-item-link" onLinkClick={this.onLinkClick}>
@@ -48,17 +48,17 @@ export class ListItem extends React.Pure
                 </span>
               )}
               <span className="ds-list-item-info">{this.props.domain}</span>
             </p>
           </div>
           <div className="ds-list-image" style={{backgroundImage: `url(${this.props.image_src})`}} />
           <ImpressionStats
             campaignId={this.props.campaignId}
-            rows={[{id: this.props.id}]}
+            rows={[{id: this.props.id, pos: this.props.pos}]}
             dispatch={this.props.dispatch}
             source={this.props.type} />
         </SafeAnchor>
       </li>
     );
   }
 }
 
@@ -75,17 +75,17 @@ export function _List(props) {
                              props.recStartingPoint + props.items).map((rec, index) => (
     <ListItem key={`ds-list-item-${index}`}
       dispatch={props.dispatch}
       campaignId={rec.campaign_id}
       domain={rec.domain}
       excerpt={rec.excerpt}
       id={rec.id}
       image_src={rec.image_src}
-      index={index}
+      pos={rec.pos}
       title={rec.title}
       context={rec.context}
       type={props.type}
       url={rec.url} />
   ));
   const listStyles = [
     "ds-list",
     props.fullWidth ? "ds-list-full-width" : "",
--- a/browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
@@ -49,17 +49,17 @@ export class ImpressionStats extends Rea
 
     if (this.props.campaignId) {
       this.props.dispatch(ac.OnlyToMain({type: at.DISCOVERY_STREAM_SPOC_IMPRESSION, data: {campaignId: this.props.campaignId}}));
     }
 
     if (this._needsImpressionStats(cards)) {
       props.dispatch(ac.DiscoveryStreamImpressionStats({
         source: props.source.toUpperCase(),
-        tiles: cards.map(link => ({id: link.id})),
+        tiles: cards.map(link => ({id: link.id, pos: link.pos})),
       }));
       this.impressionCardGuids = cards.map(link => link.id);
     }
   }
 
   setImpressionObserverOrAddListener() {
     const {props} = this;
 
--- a/browser/components/newtab/content-src/lib/selectLayoutRender.js
+++ b/browser/components/newtab/content-src/lib/selectLayoutRender.js
@@ -30,32 +30,50 @@ export const selectLayoutRender = create
           ...data,
           recommendations,
         };
       }
 
       return data;
     }
 
+    const positions = {};
+
     return layout.map(row => ({
       ...row,
 
       // Loops through all the components and adds a .data property
       // containing data from feeds
       components: row.components.map(component => {
         if (!component.feed || !feeds.data[component.feed.url]) {
           return component;
         }
 
+        positions[component.type] = positions[component.type] || 0;
+
         let {data} = feeds.data[component.feed.url];
 
         if (component && component.properties && component.properties.offset) {
           data = {
             ...data,
             recommendations: data.recommendations.slice(component.properties.offset),
           };
         }
 
-        return {...component, data: maybeInjectSpocs(data, component.spocs)};
+        data = maybeInjectSpocs(data, component.spocs);
+
+        let items = 0;
+        if (component.properties && component.properties.items) {
+          items = Math.min(component.properties.items, data.recommendations.length);
+        }
+
+        // loop through a component items
+        // Store the items position sequentially for multiple components of the same type.
+        // Example: A second card grid starts pos offset from the last card grid.
+        for (let i = 0; i < items; i++) {
+          data.recommendations[i].pos = positions[component.type]++;
+        }
+
+        return {...component, data};
       }),
     }));
   }
 );
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -214,17 +214,17 @@ const actionTypes = {};
 for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_SETUP", "DISCOVERY_STREAM_CONFIG_SET_VALUE", "DISCOVERY_STREAM_FEEDS_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_OPT_OUT", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_SEARCH", "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_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "POCKET_CTA", "POCKET_LOGGED_IN", "POCKET_WAITING_FOR_SPOC", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
   actionTypes[type] = type;
 }
 
 // These are acceptable actions for AS Router messages to have. They can show up
 // as call-to-action buttons in snippets, onboarding tour, etc.
 const ASRouterActions = {};
 
-for (const type of ["INSTALL_ADDON_FROM_URL", "OPEN_APPLICATIONS_MENU", "OPEN_PRIVATE_BROWSER_WINDOW", "OPEN_URL", "OPEN_ABOUT_PAGE", "OPEN_PREFERENCES_PAGE", "SHOW_FIREFOX_ACCOUNTS"]) {
+for (const type of ["INSTALL_ADDON_FROM_URL", "OPEN_APPLICATIONS_MENU", "OPEN_PRIVATE_BROWSER_WINDOW", "OPEN_URL", "OPEN_ABOUT_PAGE", "OPEN_PREFERENCES_PAGE", "SHOW_FIREFOX_ACCOUNTS", "PIN_CURRENT_TAB"]) {
   ASRouterActions[type] = type;
 }
 
 // Helper function for creating routed actions between content and main
 // Not intended to be used by consumers
 function _RouteMessage(action, options) {
   const meta = action.meta ? Object.assign({}, action.meta) : {};
   if (!options || !options.from || !options.to) {
@@ -3932,17 +3932,17 @@ class ImpressionStats extends react__WEB
 
     if (this.props.campaignId) {
       this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({ type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].DISCOVERY_STREAM_SPOC_IMPRESSION, data: { campaignId: this.props.campaignId } }));
     }
 
     if (this._needsImpressionStats(cards)) {
       props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].DiscoveryStreamImpressionStats({
         source: props.source.toUpperCase(),
-        tiles: cards.map(link => ({ id: link.id }))
+        tiles: cards.map(link => ({ id: link.id, pos: link.pos }))
       }));
       this.impressionCardGuids = cards.map(link => link.id);
     }
   }
 
   setImpressionObserverOrAddListener() {
     const { props } = this;
 
@@ -7294,23 +7294,23 @@ class DSCard_DSCard extends external_Rea
     this.onLinkClick = this.onLinkClick.bind(this);
   }
 
   onLinkClick(event) {
     if (this.props.dispatch) {
       this.props.dispatch(Actions["actionCreators"].UserEvent({
         event: "CLICK",
         source: this.props.type.toUpperCase(),
-        action_position: this.props.index
+        action_position: this.props.pos
       }));
 
       this.props.dispatch(Actions["actionCreators"].ImpressionStats({
         source: this.props.type.toUpperCase(),
         click: 0,
-        tiles: [{ id: this.props.id, pos: this.props.index }]
+        tiles: [{ id: this.props.id, pos: this.props.pos }]
       }));
     }
   }
 
   render() {
     return external_React_default.a.createElement(
       SafeAnchor_SafeAnchor,
       { url: this.props.url, className: "ds-card", onLinkClick: this.onLinkClick },
@@ -7353,17 +7353,17 @@ class DSCard_DSCard extends external_Rea
             "span",
             { className: "source" },
             this.props.source
           )
         )
       ),
       external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
         campaignId: this.props.campaignId,
-        rows: [{ id: this.props.id }],
+        rows: [{ id: this.props.id, pos: this.props.pos }],
         dispatch: this.props.dispatch,
         source: this.props.type })
     );
   }
 }
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
 
 
@@ -7374,23 +7374,23 @@ class CardGrid_CardGrid extends external
 
     // Handle a render before feed has been fetched by displaying nothing
     if (!data) {
       return external_React_default.a.createElement("div", null);
     }
 
     let cards = data.recommendations.slice(0, this.props.items).map((rec, index) => external_React_default.a.createElement(DSCard_DSCard, {
       key: `dscard-${index}`,
+      pos: rec.pos,
       campaignId: rec.campaign_id,
       image_src: rec.image_src,
       title: rec.title,
       excerpt: rec.excerpt,
       url: rec.url,
       id: rec.id,
-      index: index,
       type: this.props.type,
       context: rec.context,
       dispatch: this.props.dispatch,
       source: rec.domain }));
 
     let divisibility = ``;
 
     if (this.props.items % 4 === 0) {
@@ -7468,23 +7468,23 @@ class List_ListItem extends external_Rea
     this.onLinkClick = this.onLinkClick.bind(this);
   }
 
   onLinkClick(event) {
     if (this.props.dispatch) {
       this.props.dispatch(Actions["actionCreators"].UserEvent({
         event: "CLICK",
         source: this.props.type.toUpperCase(),
-        action_position: this.props.index
+        action_position: this.props.pos
       }));
 
       this.props.dispatch(Actions["actionCreators"].ImpressionStats({
         source: this.props.type.toUpperCase(),
         click: 0,
-        tiles: [{ id: this.props.id, pos: this.props.index }]
+        tiles: [{ id: this.props.id, pos: this.props.pos }]
       }));
     }
   }
 
   render() {
     return external_React_default.a.createElement(
       "li",
       { className: "ds-list-item" },
@@ -7526,17 +7526,17 @@ class List_ListItem extends external_Rea
               { className: "ds-list-item-info" },
               this.props.domain
             )
           )
         ),
         external_React_default.a.createElement("div", { className: "ds-list-image", style: { backgroundImage: `url(${this.props.image_src})` } }),
         external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
           campaignId: this.props.campaignId,
-          rows: [{ id: this.props.id }],
+          rows: [{ id: this.props.id, pos: this.props.pos }],
           dispatch: this.props.dispatch,
           source: this.props.type })
       )
     );
   }
 }
 
 /**
@@ -7550,17 +7550,17 @@ function _List(props) {
   const recs = feed.recommendations;
   let recMarkup = recs.slice(props.recStartingPoint, props.recStartingPoint + props.items).map((rec, index) => external_React_default.a.createElement(List_ListItem, { key: `ds-list-item-${index}`,
     dispatch: props.dispatch,
     campaignId: rec.campaign_id,
     domain: rec.domain,
     excerpt: rec.excerpt,
     id: rec.id,
     image_src: rec.image_src,
-    index: index,
+    pos: rec.pos,
     title: rec.title,
     context: rec.context,
     type: props.type,
     url: rec.url }));
   const listStyles = ["ds-list", props.fullWidth ? "ds-list-full-width" : "", props.hasBorders ? "ds-list-borders" : "", props.hasImages ? "ds-list-images" : "", props.hasNumbers ? "ds-list-numbers" : ""];
   return external_React_default.a.createElement(
     "div",
     null,
@@ -7601,47 +7601,46 @@ class Hero_Hero extends external_React_d
     this.onLinkClick = this.onLinkClick.bind(this);
   }
 
   onLinkClick(event) {
     if (this.props.dispatch) {
       this.props.dispatch(Actions["actionCreators"].UserEvent({
         event: "CLICK",
         source: this.props.type.toUpperCase(),
-        action_position: 0
+        action_position: this.heroRec.pos
       }));
 
       this.props.dispatch(Actions["actionCreators"].ImpressionStats({
         source: this.props.type.toUpperCase(),
         click: 0,
-        tiles: [{ id: this.heroRec.id, pos: 0 }]
+        tiles: [{ id: this.heroRec.id, pos: this.heroRec.pos }]
       }));
     }
   }
 
   render() {
     const { data } = this.props;
 
     // Handle a render before feed has been fetched by displaying nothing
     if (!data || !data.recommendations) {
       return external_React_default.a.createElement("div", null);
     }
 
     let [heroRec, ...otherRecs] = data.recommendations.slice(0, this.props.items);
     this.heroRec = heroRec;
 
-    // Note that `{index + 1}` is necessary below for telemetry since we treat heroRec as index 0.
     let cards = otherRecs.map((rec, index) => external_React_default.a.createElement(DSCard_DSCard, {
       campaignId: rec.campaign_id,
       key: `dscard-${index}`,
       image_src: rec.image_src,
       title: rec.title,
       url: rec.url,
       id: rec.id,
-      index: index + 1,
+      pos: rec.pos,
       type: this.props.type,
       dispatch: this.props.dispatch,
       context: rec.context,
       source: rec.domain }));
 
     let list = external_React_default.a.createElement(List, {
       recStartingPoint: 1,
       data: data,
@@ -7693,17 +7692,17 @@ class Hero_Hero extends external_React_d
             ) : external_React_default.a.createElement(
               "p",
               { className: "source" },
               heroRec.domain
             )
           ),
           external_React_default.a.createElement(ImpressionStats["ImpressionStats"], {
             campaignId: heroRec.campaignId,
-            rows: [{ id: heroRec.id }],
+            rows: [{ id: heroRec.id, pos: heroRec.pos }],
             dispatch: this.props.dispatch,
             source: this.props.type })
         ),
         external_React_default.a.createElement(
           "div",
           { className: `${this.props.subComponentType}` },
           this.props.subComponentType === `cards` ? cards : list
         )
@@ -7938,34 +7937,52 @@ function layoutRender(layout, feeds, spo
       return Object.assign({}, data, {
         recommendations
       });
     }
 
     return data;
   }
 
+  const positions = {};
+
   return layout.map(row => Object.assign({}, row, {
 
     // Loops through all the components and adds a .data property
     // containing data from feeds
     components: row.components.map(component => {
       if (!component.feed || !feeds.data[component.feed.url]) {
         return component;
       }
 
+      positions[component.type] = positions[component.type] || 0;
+
       let { data } = feeds.data[component.feed.url];
 
       if (component && component.properties && component.properties.offset) {
         data = Object.assign({}, data, {
           recommendations: data.recommendations.slice(component.properties.offset)
         });
       }
 
-      return Object.assign({}, component, { data: maybeInjectSpocs(data, component.spocs) });
+      data = maybeInjectSpocs(data, component.spocs);
+
+      let items = 0;
+      if (component.properties && component.properties.items) {
+        items = Math.min(component.properties.items, data.recommendations.length);
+      }
+
+      // loop through a component items
+      // Store the items position sequentially for multiple components of the same type.
+      // Example: A second card grid starts pos offset from the last card grid.
+      for (let i = 0; i < items; i++) {
+        data.recommendations[i].pos = positions[component.type]++;
+      }
+
+      return Object.assign({}, component, { data });
     })
   }));
 });
 // EXTERNAL MODULE: ./content-src/components/TopSites/TopSites.jsx
 var TopSites = __webpack_require__(31);
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/TopSites/TopSites.jsx
 
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -1034,16 +1034,19 @@ class _ASRouter {
         break;
       case ra.OPEN_APPLICATIONS_MENU:
         UITour.showMenu(target.browser.ownerGlobal, action.data.args);
         break;
       case ra.INSTALL_ADDON_FROM_URL:
         this._updateOnboardingState();
         await MessageLoaderUtils.installAddonFromURL(target.browser, action.data.url);
         break;
+      case ra.PIN_CURRENT_TAB:
+        target.browser.ownerGlobal.gBrowser.pinTab(target.browser.ownerGlobal.gBrowser.selectedTab);
+        break;
       case ra.SHOW_FIREFOX_ACCOUNTS:
         const url = await FxAccounts.config.promiseSignUpURI("snippets");
         // We want to replace the current tab.
         target.browser.ownerGlobal.openLinkIn(url, "current", {
           private: false,
           triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
         });
         break;
--- a/browser/components/newtab/lib/ASRouterTargeting.jsm
+++ b/browser/components/newtab/lib/ASRouterTargeting.jsm
@@ -287,35 +287,54 @@ const TargetingGetters = {
     return parseInt(AppConstants.MOZ_APP_VERSION.match(/\d+/), 10);
   },
   get region() {
     return Services.prefs.getStringPref(SEARCH_REGION_PREF, "");
   },
   get needsUpdate() {
     return QueryCache.queries.CheckBrowserNeedsUpdate.get();
   },
+  get hasPinnedTabs() {
+    for (let win of Services.wm.getEnumerator("navigator:browser")) {
+      if (win.closed) {
+        continue;
+      }
+      if (win.ownerGlobal.gBrowser.visibleTabs.filter(t => t.pinned).length) {
+        return true;
+      }
+    }
+
+    return false;
+  },
 };
 
 this.ASRouterTargeting = {
   Environment: TargetingGetters,
 
   ERROR_TYPES: {
     MALFORMED_EXPRESSION: "MALFORMED_EXPRESSION",
     OTHER_ERROR: "OTHER_ERROR",
   },
 
-  isMatch(filterExpression, customContext) {
-    let context = this.Environment;
-    if (customContext) {
-      context = {};
-      Object.defineProperties(context, Object.getOwnPropertyDescriptors(this.Environment));
-      Object.defineProperties(context, Object.getOwnPropertyDescriptors(customContext));
+  // Combines the getter properties of two objects without evaluating them
+  combineContexts(contextA = {}, contextB = {}) {
+    const sameProperty = Object.keys(contextA).find(p => Object.keys(contextB).includes(p));
+    if (sameProperty) {
+      Cu.reportError(`Property ${sameProperty} exists in both contexts and is overwritten.`);
     }
 
-    return FilterExpressions.eval(filterExpression, context);
+    const context = {};
+    Object.defineProperties(context, Object.getOwnPropertyDescriptors(contextA));
+    Object.defineProperties(context, Object.getOwnPropertyDescriptors(contextB));
+
+    return context;
+  },
+
+  isMatch(filterExpression, customContext) {
+    return FilterExpressions.eval(filterExpression, this.combineContexts(this.Environment, customContext));
   },
 
   isTriggerMatch(trigger = {}, candidateMessageTrigger = {}) {
     if (trigger.id !== candidateMessageTrigger.id) {
       return false;
     } else if (!candidateMessageTrigger.params) {
       return true;
     }
@@ -357,24 +376,26 @@ this.ASRouterTargeting = {
    * @param {obj} impressions An object containing impressions, where keys are message ids
    * @param {trigger} string A trigger expression if a message for that trigger is desired
    * @param {obj|null} context A FilterExpression context. Defaults to TargetingGetters above.
    * @returns {obj} an AS router message
    */
   async findMatchingMessage({messages, trigger, context, onError}) {
     const weightSortedMessages = sortMessagesByWeightedRank([...messages]);
     const sortedMessages = sortMessagesByTargeting(weightSortedMessages);
+    const triggerContext = trigger ? trigger.context : {};
+    const combinedContext = this.combineContexts(context, triggerContext);
 
     for (const candidate of sortedMessages) {
       if (
         candidate &&
         (trigger ? this.isTriggerMatch(trigger, candidate.trigger) : !candidate.trigger) &&
         // If a trigger expression was passed to this function, the message should match it.
         // Otherwise, we should choose a message with no trigger property (i.e. a message that can show up at any time)
-        await this.checkMessageTargeting(candidate, context, onError)
+        await this.checkMessageTargeting(candidate, combinedContext, onError)
       ) {
         return candidate;
       }
     }
 
     return null;
   },
 };
--- a/browser/components/newtab/lib/ASRouterTriggerListeners.jsm
+++ b/browser/components/newtab/lib/ASRouterTriggerListeners.jsm
@@ -3,68 +3,228 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
+const FEW_MINUTES = 15 * 60 * 1000; // 15 mins
+
+/**
+ * Wait for browser startup to finish to avoid accessing uninitialized
+ * properties
+ */
+async function checkStartupFinished(win) {
+  if (!win.gBrowserInit.delayedStartupFinished) {
+    await new Promise(resolve => {
+      let delayedStartupObserver = (subject, topic) => {
+        if (topic === "browser-delayed-startup-finished" && subject === win) {
+          Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
+          resolve();
+        }
+      };
+
+      Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished");
+    });
+  }
+}
+
+function isPrivateWindow(win) {
+  return !(win instanceof Ci.nsIDOMWindow) || win.closed || PrivateBrowsingUtils.isWindowPrivate(win);
+}
+
 /**
  * A Map from trigger IDs to singleton trigger listeners. Each listener must
  * have idempotent `init` and `uninit` methods.
  */
 this.ASRouterTriggerListeners = new Map([
+  ["frequentVisits", {
+    _initialized: false,
+    _triggerHandler: null,
+    _hosts: null,
+    _visits: null,
+
+    async init(triggerHandler, hosts) {
+      if (this._initialized) {
+        return;
+      }
+      this.onTabSwitch = this.onTabSwitch.bind(this);
+
+      // Add listeners to all existing browser windows
+      for (let win of Services.wm.getEnumerator("navigator:browser")) {
+        if (isPrivateWindow(win)) {
+          continue;
+        }
+        await checkStartupFinished(win);
+        win.addEventListener("TabSelect", this.onTabSwitch);
+        win.gBrowser.addTabsProgressListener(this);
+      }
+
+      this._initialized = true;
+      this._triggerHandler = triggerHandler;
+      this._visits = new Map();
+      if (this._hosts) {
+        hosts.forEach(h => this._hosts.add(h));
+      } else {
+        this._hosts = new Set(hosts); // Clone the hosts to avoid unexpected behaviour
+      }
+    },
+
+    /* _updateVisits - Record visit timestamps for websites that match `this._hosts` and only
+     * if it's been more than FEW_MINUTES since the last visit.
+     * @param {string} host - Location host of current selected tab
+     * @returns {boolean} - If the new visit has been recorded
+     */
+    _updateVisits(host) {
+      const visits = this._visits.get(host);
+
+      if (visits && Date.now() - visits[0] > FEW_MINUTES) {
+        this._visits.set(host, [Date.now(), ...visits]);
+        return true;
+      }
+      if (!visits) {
+        this._visits.set(host, [Date.now()]);
+        return true;
+      }
+
+      return false;
+    },
+
+    onTabSwitch(event) {
+      if (!event.target.ownerGlobal.gBrowser) {
+        return;
+      }
+
+      let host;
+      const {gBrowser} = event.target.ownerGlobal;
+
+      try {
+        // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+        host = gBrowser.currentURI.host;
+      } catch (e) {} // Couldn't parse location URL
+
+      if (host && this._hosts.has(host)) {
+        this.triggerHandler(gBrowser.selectedBrowser, host);
+      }
+    },
+
+    triggerHandler(aBrowser, host) {
+      const updated = this._updateVisits(host);
+
+      // If the previous visit happend less than FEW_MINUTES ago
+      // no updates were made, no need to trigger the handler
+      if (!updated) {
+        return;
+      }
+
+      this._triggerHandler(aBrowser, {
+        id: "frequentVisits",
+        param: host,
+        context: {
+          // Remapped to {host, timestamp} because JEXL operators can only
+          // filter over collections (arrays of objects)
+          recentVisits: this._visits.get(host).map(timestamp => ({host, timestamp})),
+        },
+      });
+    },
+
+    onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
+      const location = aLocationURI ? aLocationURI.spec : "";
+      // Some websites trigger redirect events after they finish loading even
+      // though the location remains the same. This results in onLocationChange
+      // events to be fired twice.
+      const isSameDocument = !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
+      if (location && aWebProgress.isTopLevel && !isSameDocument) {
+        try {
+          const host = (new URL(location)).hostname;
+          if (host && this._hosts.has(host)) {
+            this.triggerHandler(aBrowser, host);
+          }
+        } catch (e) {} // Couldn't parse location URL
+      }
+    },
+
+    observe(win, topic, data) {
+      let onLoad;
+
+      switch (topic) {
+        case "domwindowopened":
+          if (isPrivateWindow(win)) {
+            break;
+          }
+          onLoad = () => {
+            // Ignore non-browser windows.
+            if (win.document.documentElement.getAttribute("windowtype") === "navigator:browser") {
+              win.addEventListener("TabSelect", this.onTabSwitch);
+              win.gBrowser.addTabsProgressListener(this);
+            }
+          };
+          win.addEventListener("load", onLoad, {once: true});
+          break;
+
+        case "domwindowclosed":
+          if ((win instanceof Ci.nsIDOMWindow) &&
+              win.document.documentElement.getAttribute("windowtype") === "navigator:browser") {
+            win.removeEventListener("TabSelect", this.onTabSwitch);
+            win.gBrowser.removeTabsProgressListener(this);
+          }
+          break;
+      }
+    },
+
+    uninit() {
+      if (this._initialized) {
+        Services.ww.unregisterNotification(this);
+
+        for (let win of Services.wm.getEnumerator("navigator:browser")) {
+          if (isPrivateWindow(win)) {
+            continue;
+          }
+
+          win.removeEventListener("TabSelect", this.onTabSwitch);
+          win.gBrowser.removeTabsProgressListener(this);
+        }
+
+        this._initialized = false;
+        this._triggerHandler = null;
+        this._hosts = null;
+        this._visits = null;
+      }
+    },
+  }],
 
   /**
    * Attach listeners to every browser window to detect location changes, and
    * notify the trigger handler whenever we navigate to a URL with a hostname
    * we're looking for.
    */
   ["openURL", {
     _initialized: false,
     _triggerHandler: null,
     _hosts: null,
 
-    /**
-     * Wait for browser startup to finish to avoid accessing uninitialized
-     * properties
-     */
-    async _checkStartupFinished(win) {
-      if (!win.gBrowserInit.delayedStartupFinished) {
-        await new Promise(resolve => {
-          let delayedStartupObserver = (subject, topic) => {
-            if (topic === "browser-delayed-startup-finished" && subject === win) {
-              Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
-              resolve();
-            }
-          };
-
-          Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished");
-        });
-      }
-    },
-
     /*
      * If the listener is already initialised, `init` will replace the trigger
      * handler and add any new hosts to `this._hosts`.
      */
     async init(triggerHandler, hosts = []) {
       if (!this._initialized) {
         this.onLocationChange = this.onLocationChange.bind(this);
 
         // Listen for new windows being opened
         Services.ww.registerNotification(this);
 
         // Add listeners to all existing browser windows
         for (let win of Services.wm.getEnumerator("navigator:browser")) {
-          if (win.closed || PrivateBrowsingUtils.isWindowPrivate(win)) {
+          if (isPrivateWindow(win)) {
             continue;
           }
-          await this._checkStartupFinished(win);
+          await checkStartupFinished(win);
           win.gBrowser.addTabsProgressListener(this);
         }
 
         this._initialized = true;
       }
       this._triggerHandler = triggerHandler;
       if (this._hosts) {
         hosts.forEach(h => this._hosts.add(h));
@@ -73,17 +233,17 @@ this.ASRouterTriggerListeners = new Map(
       }
     },
 
     uninit() {
       if (this._initialized) {
         Services.ww.unregisterNotification(this);
 
         for (let win of Services.wm.getEnumerator("navigator:browser")) {
-          if (win.closed || PrivateBrowsingUtils.isWindowPrivate(win)) {
+          if (isPrivateWindow(win)) {
             continue;
           }
 
           win.gBrowser.removeTabsProgressListener(this);
         }
 
         this._initialized = false;
         this._triggerHandler = null;
@@ -107,17 +267,17 @@ this.ASRouterTriggerListeners = new Map(
       }
     },
 
     observe(win, topic, data) {
       let onLoad;
 
       switch (topic) {
         case "domwindowopened":
-          if (!(win instanceof Ci.nsIDOMWindow) || win.closed || PrivateBrowsingUtils.isWindowPrivate(win)) {
+          if (isPrivateWindow(win)) {
             break;
           }
           onLoad = () => {
             // Ignore non-browser windows.
             if (win.document.documentElement.getAttribute("windowtype") === "navigator:browser") {
               win.gBrowser.addTabsProgressListener(this);
             }
           };
--- a/browser/components/newtab/lib/ActivityStream.jsm
+++ b/browser/components/newtab/lib/ActivityStream.jsm
@@ -127,16 +127,25 @@ const PREFS_CONFIG = new Map([
     value: true,
     value_local_dev: false,
   }],
   ["telemetry.ut.events", {
     title: "Enable Unified Telemetry event data collection",
     value: AppConstants.EARLY_BETA_OR_EARLIER,
     value_local_dev: false,
   }],
+  ["telemetry.structuredIngestion", {
+    title: "Enable Structured Ingestion Telemetry data collection",
+    value: AppConstants.EARLY_BETA_OR_EARLIER,
+    value_local_dev: false,
+  }],
+  ["telemetry.structuredIngestion.endpoint", {
+    title: "Structured Ingestion telemetry server endpoint",
+    value: "https://incoming.telemetry.mozilla.org/submit/activity-stream",
+  }],
   ["telemetry.ping.endpoint", {
     title: "Telemetry server endpoint",
     value: "https://tiles.services.mozilla.com/v4/links/activity-stream",
   }],
   ["section.highlights.includeVisited", {
     title: "Boolean flag that decides whether or not to show visited pages in highlights.",
     value: true,
   }],
--- a/browser/components/newtab/lib/CFRMessageProvider.jsm
+++ b/browser/components/newtab/lib/CFRMessageProvider.jsm
@@ -32,16 +32,17 @@ const WIKIPEDIA_CONTEXT_MENU_SEARCH_PARA
   min_frecency: 10000,
 };
 const REDDIT_ENHANCEMENT_PARAMS = {
   existing_addons: ["jid1-xUfzOsOFlzSOXg@jetpack"],
   open_urls: ["www.reddit.com", "reddit.com"],
   sumo_path: "extensionrecommendations",
   min_frecency: 10000,
 };
+const PINNED_TABS_TARGET_SITES = ["trello.com", "www.trello.com", "wunderlist.com", "www.wunderlist.com", "docs.google.com", "www.docs.google.com", "calendar.google.com", "www.calendar.google.com", "simplenote.com", "www.simplenote.com", "airtable.com", "www.airtable.com", "todoist.com", "www.todoist.com", "slack.com", "www.slack.com", "irccloud.com", "www.irccloud.com", "products.office.com", "www.products.office.com", "messenger.com", "www.messenger.com", "discordapp.com", "www.discordapp.com", "web.wechat.com", "www.web.wechat.com", "web.whatsapp.com", "www.web.whatsapp.com", "gmail.com", "www.gmail.com", "mail.yahoo.com", "www.mail.yahoo.com", "outlook.com", "www.outlook.com", "polymail.io", "www.polymail.io", "icloud.com", "www.icloud.com", "mail.aol.com", "www.mail.aol.com", "lightroom.adobe.com", "www.lightroom.adobe.com", "facebook.com", "www.facebook.com", "twitter.com", "www.twitter.com", "instagram.com", "www.instagram.com", "pinterest.com", "www.pinterest.com", "reddit.com", "www.reddit.com", "coursera.org", "www.coursera.org", "edx.org", "www.edx.org", "udemy.com", "www.udemy.com", "skillshare.com", "www.skillshare.com", "pluralsight.com", "www.pluralsight.com", "udacity.com", "www.udacity.com", "tumblr.com", "www.tumblr.com", "quora.com", "www.quora.com", "deviantart.com", "www.deviantart.com", "github.com", "www.github.com", "kaggle.com", "www.kaggle.com", "dropbox.com", "www.dropbox.com", "drive.google.com", "www.drive.google.com", "box.com", "www.box.com", "netflix.com", "www.netflix.com", "primevideo.com", "www.primevideo.com", "hulu.com", "www.hulu.com", "crave.ca", "www.crave.ca", "twitch.tv", "www.twitch.tv", "youtube.com", "www.youtube.com", "craigslist.org", "www.craigslist.org", "kijiji.ca", "www.kijiji.ca"];
 
 const CFR_MESSAGES = [
   {
     id: "FACEBOOK_CONTAINER_3",
     template: "cfr_doorhanger",
     content: {
       bucket_id: "CFR_M1",
       notification_text: {string_id: "cfr-doorhanger-extension-notification"},
@@ -291,16 +292,53 @@ const CFR_MESSAGES = [
     frequency: {lifetime: 3},
     targeting: `
       localeLanguageCode == "en" &&
       (xpinstallEnabled == true) &&
       (${JSON.stringify(REDDIT_ENHANCEMENT_PARAMS.existing_addons)} intersect addonsInfo.addons|keys)|length == 0 &&
       (${JSON.stringify(REDDIT_ENHANCEMENT_PARAMS.open_urls)} intersect topFrecentSites[.frecency >= ${REDDIT_ENHANCEMENT_PARAMS.min_frecency}]|mapToProperty('host'))|length > 0`,
     trigger: {id: "openURL", params: REDDIT_ENHANCEMENT_PARAMS.open_urls},
   },
+  {
+    id: "PIN_TAB",
+    template: "cfr_doorhanger",
+    exclude: true,
+    content: {
+      bucket_id: "CFR_PIN_TAB",
+      notification_text: {string_id: "cfr-doorhanger-extension-notification"},
+      heading_text: {string_id: "cfr-doorhanger-pintab-heading"},
+      info_icon: {
+        label: {string_id: "cfr-doorhanger-extension-sumo-link"},
+        sumo_path: REDDIT_ENHANCEMENT_PARAMS.sumo_path,
+      },
+      text: "Get easy access to your most-used sites. Keep sites open in a tab (even when you restart).",
+      buttons: {
+        primary: {
+          label: {string_id: "cfr-doorhanger-pintab-ok-button"},
+          action: {
+            type: "PIN_CURRENT_TAB",
+          },
+        },
+        secondary: [{
+          label: {string_id: "cfr-doorhanger-extension-cancel-button"},
+          action: {type: "CANCEL"},
+        }, {
+          label: {string_id: "cfr-doorhanger-extension-never-show-recommendation"},
+        }, {
+          label: {string_id: "cfr-doorhanger-extension-manage-settings-button"},
+          action: {
+            type: "OPEN_PREFERENCES_PAGE",
+            data: {category: "general-cfr", origin: "CFR"},
+          },
+        }],
+      },
+    },
+    targeting: `!hasPinnedTabs && recentVisits[.timestamp > (currentDate|date - 3600 * 1000 * 1)]|length >= 1`,
+    trigger: {id: "frequentVisits", params: PINNED_TABS_TARGET_SITES},
+  },
 ];
 
 const CFRMessageProvider = {
   getMessages() {
     return CFR_MESSAGES.filter(msg => !msg.exclude);
   },
 };
 this.CFRMessageProvider = CFRMessageProvider;
--- a/browser/components/newtab/lib/CFRPageActions.jsm
+++ b/browser/components/newtab/lib/CFRPageActions.jsm
@@ -44,39 +44,39 @@ class PageAction {
     this.label = win.document.getElementById("cfr-label");
 
     // This should NOT be use directly to dispatch message-defined actions attached to buttons.
     // Please use dispatchUserAction instead.
     this._dispatchToASRouter = dispatchToASRouter;
 
     this._popupStateChange = this._popupStateChange.bind(this);
     this._collapse = this._collapse.bind(this);
-    this._handleClick = this._handleClick.bind(this);
+    this._showPopupOnClick = this._showPopupOnClick.bind(this);
     this.dispatchUserAction = this.dispatchUserAction.bind(this);
 
     this._l10n = new Localization([
       "browser/newtab/asrouter.ftl",
     ]);
 
     // Saved timeout IDs for scheduled state changes, so they can be cancelled
     this.stateTransitionTimeoutIDs = [];
   }
 
-  async show(recommendation, shouldExpand = false) {
+  async showAddressBarNotifier(recommendation, shouldExpand = false) {
     this.container.hidden = false;
 
     this.label.value = await this.getStrings(recommendation.content.notification_text);
 
     // Wait for layout to flush to avoid a synchronous reflow then calculate the
     // label width. We can safely get the width even though the recommendation is
     // collapsed; the label itself remains full width (with its overflow hidden)
     let [{width}] = await this.window.promiseDocumentFlushed(() => this.label.getClientRects());
     this.urlbar.style.setProperty("--cfr-label-width", `${width}px`);
 
-    this.container.addEventListener("click", this._handleClick);
+    this.container.addEventListener("click", this._showPopupOnClick);
     // Collapse the recommendation on url bar focus in order to free up more
     // space to display and edit the url
     this.urlbar.addEventListener("focus", this._collapse);
 
     if (shouldExpand) {
       this._clearScheduledStateChanges();
 
       // After one second, expand
@@ -88,21 +88,21 @@ class PageAction {
       // page (both `bucket_id` and `id` will be set as null), we don't want to send
       // the impression ping in that case.
       if (!!recommendation.id && !!recommendation.content.bucket_id) {
         this._sendTelemetry({message_id: recommendation.id, bucket_id: recommendation.content.bucket_id, event: "IMPRESSION"});
       }
     }
   }
 
-  hide() {
+  hideAddressBarNotifier() {
     this.container.hidden = true;
     this._clearScheduledStateChanges();
     this.urlbar.removeAttribute("cfr-recommendation-state");
-    this.container.removeEventListener("click", this._handleClick);
+    this.container.removeEventListener("click", this._showPopupOnClick);
     this.urlbar.removeEventListener("focus", this._collapse);
     if (this.currentNotification) {
       this.window.PopupNotifications.remove(this.currentNotification);
       this.currentNotification = null;
     }
   }
 
   _expand(delay) {
@@ -216,37 +216,18 @@ class PageAction {
         return acc;
       }, {});
       mainString.attributes = attributes;
     }
 
     return subAttribute ? mainString.attributes[subAttribute] : mainString;
   }
 
-  /**
-   * Respond to a user click on the recommendation by showing a doorhanger/
-   * popup notification
-   */
-  async _handleClick(event) { // eslint-disable-line max-statements
-    const browser = this.window.gBrowser.selectedBrowser;
-    if (!RecommendationMap.has(browser)) {
-      // There's no recommendation for this browser, so the user shouldn't have
-      // been able to click
-      this.hide();
-      return;
-    }
-    const {id, content} = RecommendationMap.get(browser);
-
-    // The recommendation should remain either collapsed or expanded while the
-    // doorhanger is showing
-    this._clearScheduledStateChanges();
-
-    // A hacky way of setting the popup anchor outside the usual url bar icon box
-    // See https://searchfox.org/mozilla-central/rev/847b64cc28b74b44c379f9bff4f415b97da1c6d7/toolkit/modules/PopupNotifications.jsm#42
-    browser.cfrpopupnotificationanchor = this.container;
+  async _renderPopup(message, browser) { // eslint-disable-line max-statements
+    const {id, content} = message;
 
     const headerLabel = this.window.document.getElementById("cfr-notification-header-label");
     const headerLink = this.window.document.getElementById("cfr-notification-header-link");
     const headerImage = this.window.document.getElementById("cfr-notification-header-image");
     const author = this.window.document.getElementById("cfr-notification-author");
     const footerText = this.window.document.getElementById("cfr-notification-footer-text");
     const footerFilledStars = this.window.document.getElementById("cfr-notification-footer-filled-stars");
     const footerEmptyStars = this.window.document.getElementById("cfr-notification-footer-empty-stars");
@@ -324,17 +305,17 @@ class PageAction {
 
     const mainAction = {
       label: primaryBtnStrings,
       accessKey: primaryBtnStrings.attributes.accesskey,
       callback: async () => {
         primary.action.data.url = await CFRPageActions._fetchLatestAddonVersion(content.addon.id); // eslint-disable-line no-use-before-define
         this._blockMessage(id);
         this.dispatchUserAction(primary.action);
-        this.hide();
+        this.hideAddressBarNotifier();
         this._sendTelemetry({message_id: id, bucket_id: content.bucket_id, event: "INSTALL"});
         RecommendationMap.delete(browser);
       },
     };
 
     const secondaryActions = [{
       label: secondaryBtnStrings[0].label,
       accessKey: secondaryBtnStrings[0].attributes.accesskey,
@@ -342,17 +323,17 @@ class PageAction {
         this.dispatchUserAction(secondary[0].action);
         this._sendTelemetry({message_id: id, bucket_id: content.bucket_id, event: "DISMISS"});
       },
     }, {
       label: secondaryBtnStrings[1].label,
       accessKey: secondaryBtnStrings[1].attributes.accesskey,
       callback: () => {
         this._blockMessage(id);
-        this.hide();
+        this.hideAddressBarNotifier();
         this._sendTelemetry({message_id: id, bucket_id: content.bucket_id, event: "BLOCK"});
         RecommendationMap.delete(browser);
       },
     }, {
       label: secondaryBtnStrings[2].label,
       accessKey: secondaryBtnStrings[2].attributes.accesskey,
       callback: () => {
         this.dispatchUserAction(secondary[2].action);
@@ -361,27 +342,54 @@ class PageAction {
     }];
 
     const options = {
       popupIconURL: content.addon.icon,
       hideClose: true,
       eventCallback: this._popupStateChange,
     };
 
-    this._sendTelemetry({message_id: id, bucket_id: content.bucket_id, event: "CLICK_DOORHANGER"});
+    // Actually show the notification
     this.currentNotification = this.window.PopupNotifications.show(
       browser,
       POPUP_NOTIFICATION_ID,
       await this.getStrings(content.addon.title),
       "cfr",
       mainAction,
       secondaryActions,
       options
     );
   }
+
+  /**
+   * Respond to a user click on the recommendation by showing a doorhanger/
+   * popup notification
+   */
+  async _showPopupOnClick(event) {
+    const browser = this.window.gBrowser.selectedBrowser;
+    if (!RecommendationMap.has(browser)) {
+      // There's no recommendation for this browser, so the user shouldn't have
+      // been able to click
+      this.hideAddressBarNotifier();
+      return;
+    }
+    const message = RecommendationMap.get(browser);
+    const {id, content} = message;
+
+    // The recommendation should remain either collapsed or expanded while the
+    // doorhanger is showing
+    this._clearScheduledStateChanges(browser, message);
+
+    // A hacky way of setting the popup anchor outside the usual url bar icon box
+    // See https://searchfox.org/mozilla-central/rev/847b64cc28b74b44c379f9bff4f415b97da1c6d7/toolkit/modules/PopupNotifications.jsm#42
+    browser.cfrpopupnotificationanchor = this.container;
+
+    this._sendTelemetry({message_id: id, bucket_id: content.bucket_id, event: "CLICK_DOORHANGER"});
+    await this._renderPopup(message, browser);
+  }
 }
 
 function isHostMatch(browser, host) {
   return (browser.documentURI.scheme.startsWith("http") &&
     browser.documentURI.host === host);
 }
 
 const CFRPageActions = {
@@ -399,31 +407,31 @@ const CFRPageActions = {
     if (!pageAction || browser !== win.gBrowser.selectedBrowser) {
       return;
     }
     if (RecommendationMap.has(browser)) {
       const recommendation = RecommendationMap.get(browser);
       if (isHostMatch(browser, recommendation.host)) {
         // The browser has a recommendation specified with this host, so show
         // the page action
-        pageAction.show(recommendation);
+        pageAction.showAddressBarNotifier(recommendation);
       } else if (recommendation.retain) {
         // Keep the recommendation first time the user navigates away just in
         // case they will go back to the previous page
-        pageAction.hide();
+        pageAction.hideAddressBarNotifier();
         recommendation.retain = false;
       } else {
         // The user has navigated away from the specified host in the given
         // browser, so the recommendation is no longer valid and should be removed
         RecommendationMap.delete(browser);
-        pageAction.hide();
+        pageAction.hideAddressBarNotifier();
       }
     } else {
       // There's no recommendation specified for this browser, so hide the page action
-      pageAction.hide();
+      pageAction.hideAddressBarNotifier();
     }
   },
 
   /**
    * Fetch the URL to the latest add-on xpi so the recommendation can download it.
    * @param id          The add-on ID
    * @return            A string for the URL that was fetched
    */
@@ -451,17 +459,17 @@ const CFRPageActions = {
   async forceRecommendation(browser, recommendation, dispatchToASRouter) {
     // If we are forcing via the Admin page, the browser comes in a different format
     const win = browser.browser.ownerGlobal;
     const {id, content} = recommendation;
     RecommendationMap.set(browser.browser, {id, retain: true, content});
     if (!PageActionMap.has(win)) {
       PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
     }
-    await PageActionMap.get(win).show(recommendation, true);
+    await PageActionMap.get(win).showAddressBarNotifier(recommendation, true);
     return true;
   },
 
   /**
    * Add a recommendation specific to the given browser and host.
    * @param browser                 The browser for the recommendation
    * @param host                    The host for the recommendation
    * @param recommendation  The recommendation to show
@@ -476,30 +484,30 @@ const CFRPageActions = {
     if (browser !== win.gBrowser.selectedBrowser || !isHostMatch(browser, host)) {
       return false;
     }
     const {id, content} = recommendation;
     RecommendationMap.set(browser, {id, host, retain: true, content});
     if (!PageActionMap.has(win)) {
       PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
     }
-    await PageActionMap.get(win).show(recommendation, true);
+    await PageActionMap.get(win).showAddressBarNotifier(recommendation, true);
     return true;
   },
 
   /**
    * Clear all recommendations and hide all PageActions
    */
   clearRecommendations() {
     // WeakMaps aren't iterable so we have to test all existing windows
     for (const win of Services.wm.getEnumerator("navigator:browser")) {
       if (win.closed || !PageActionMap.has(win)) {
         continue;
       }
-      PageActionMap.get(win).hide();
+      PageActionMap.get(win).hideAddressBarNotifier();
     }
     // WeakMaps don't have a `clear` method
     PageActionMap = new WeakMap();
     RecommendationMap = new WeakMap();
     this.PageActionMap = PageActionMap;
     this.RecommendationMap = RecommendationMap;
   },
 };
--- a/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
+++ b/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
@@ -304,21 +304,46 @@ this.DiscoveryStreamFeed = class Discove
       lastUpdated: Date.now(),
       data: {},
     };
 
     sendUpdate({
       type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
       data: {
         lastUpdated: spocs.lastUpdated,
-        spocs: this.filterSpocs(spocs.data),
+        spocs: this.transform(this.filterSpocs(spocs.data)),
       },
     });
   }
 
+  transform(data) {
+    if (data && data.spocs && data.spocs.length) {
+      const spocsPerDomain = this.store.getState().DiscoveryStream.spocs.spocs_per_domain || 1;
+      const campaignMap = {};
+      return {
+        ...data,
+        spocs: data.spocs
+          .map(s => ({...s, score: s.item_score}))
+          .filter(s => s.score >= s.min_score)
+          .sort((a, b) => b.score - a.score)
+          .filter(s => {
+            if (!campaignMap[s.campaign_id]) {
+              campaignMap[s.campaign_id] = 1;
+              return true;
+            } else if (campaignMap[s.campaign_id] < spocsPerDomain) {
+              campaignMap[s.campaign_id]++;
+              return true;
+            }
+            return false;
+          }),
+      };
+    }
+    return data;
+  }
+
   // Filter spocs based on frequency caps
   filterSpocs(data) {
     if (data && data.spocs && data.spocs.length) {
       const {spocs} = data;
       const impressions = this.readImpressionsPref(PREF_SPOC_IMPRESSIONS);
       return {
         ...data,
         spocs: spocs.filter(s => this.isBelowFrequencyCap(impressions, s)),
@@ -681,17 +706,17 @@ this.DiscoveryStreamFeed = class Discove
 
           const cachedData = await this.cache.get() || {};
           const {spocs} = cachedData;
 
           this.store.dispatch(ac.AlsoToPreloaded({
             type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
             data: {
               lastUpdated: spocs.lastUpdated,
-              spocs: this.filterSpocs(spocs.data),
+              spocs: this.transform(this.filterSpocs(spocs.data)),
             },
           }));
         }
         break;
       case at.UNINIT:
         // When this feed is shutting down:
         this.uninitPrefs();
         break;
--- a/browser/components/newtab/lib/FaviconFeed.jsm
+++ b/browser/components/newtab/lib/FaviconFeed.jsm
@@ -148,17 +148,20 @@ this.FaviconFeed = class FaviconFeed {
       Services.scriptSecurityManager.getSystemPrincipal()
     );
   }
 
   /**
    * Get the site tippy top data from Remote Settings.
    */
   async getSite(domain) {
-    const sites = await this.tippyTop.get({filters: {domain}});
+    const sites = await this.tippyTop.get({
+      filters: {domain},
+      syncIfEmpty: false,
+    });
     return sites.length ? sites[0] : null;
   }
 
   /**
    * Get the tippy top collection from Remote Settings.
    */
   get tippyTop() {
     if (!this._tippyTop) {
--- a/browser/components/newtab/lib/TelemetryFeed.jsm
+++ b/browser/components/newtab/lib/TelemetryFeed.jsm
@@ -49,34 +49,46 @@ const USER_PREFS_ENCODING = {
   "feeds.section.highlights": 1 << 3,
   "feeds.snippets": 1 << 4,
   "showSponsored": 1 << 5,
 };
 
 const PREF_IMPRESSION_ID = "impressionId";
 const TELEMETRY_PREF = "telemetry";
 const EVENTS_TELEMETRY_PREF = "telemetry.ut.events";
+const STRUCTURED_INGESTION_TELEMETRY_PREF = "telemetry.structuredIngestion";
+const STRUCTURED_INGESTION_ENDPOINT_PREF = "telemetry.structuredIngestion.endpoint";
 
 this.TelemetryFeed = class TelemetryFeed {
   constructor(options) {
     this.sessions = new Map();
     this._prefs = new Prefs();
     this._impressionId = this.getOrCreateImpressionId();
-    this.telemetryEnabled = this._prefs.get(TELEMETRY_PREF);
-    this.eventTelemetryEnabled = this._prefs.get(EVENTS_TELEMETRY_PREF);
     this._aboutHomeSeen = false;
-    this._onTelemetryPrefChange = this._onTelemetryPrefChange.bind(this);
-    this._prefs.observe(TELEMETRY_PREF, this._onTelemetryPrefChange);
-    this._onEventsTelemetryPrefChange = this._onEventsTelemetryPrefChange.bind(this);
-    this._prefs.observe(EVENTS_TELEMETRY_PREF, this._onEventsTelemetryPrefChange);
     this._classifySite = classifySite;
     this._addWindowListeners = this._addWindowListeners.bind(this);
     this.handleEvent = this.handleEvent.bind(this);
   }
 
+  get telemetryEnabled() {
+    return this._prefs.get(TELEMETRY_PREF);
+  }
+
+  get eventTelemetryEnabled() {
+    return this._prefs.get(EVENTS_TELEMETRY_PREF);
+  }
+
+  get structuredIngestionTelemetryEnabled() {
+    return this._prefs.get(STRUCTURED_INGESTION_TELEMETRY_PREF);
+  }
+
+  get structuredIngestionEndpointBase() {
+    return this._prefs.get(STRUCTURED_INGESTION_ENDPOINT_PREF);
+  }
+
   init() {
     Services.obs.addObserver(this.browserOpenNewtabStart, "browser-open-newtab-start");
     // Add pin tab event listeners on future windows
     Services.obs.addObserver(this._addWindowListeners, DOMWINDOW_OPENED_TOPIC);
     // Listen for pin tab events on all open windows
     for (let win of Services.wm.getEnumerator("navigator:browser")) {
       this._addWindowListeners(win);
     }
@@ -180,24 +192,16 @@ this.TelemetryFeed = class TelemetryFeed
       };
     } catch (e) {
       // if no mark was returned, we have nothing to save
       return;
     }
     this.saveSessionPerfData(port, data_to_save);
   }
 
-  _onTelemetryPrefChange(prefVal) {
-    this.telemetryEnabled = prefVal;
-  }
-
-  _onEventsTelemetryPrefChange(prefVal) {
-    this.eventTelemetryEnabled = prefVal;
-  }
-
   /**
    * Lazily initialize PingCentre for Activity Stream to send pings
    */
   get pingCentre() {
     Object.defineProperty(this, "pingCentre",
       {
         value: new PingCentre({
           topic: ACTIVITY_STREAM_ID,
@@ -359,17 +363,19 @@ this.TelemetryFeed = class TelemetryFeed
   sendDiscoveryStreamImpressions(port, session) {
     const {impressionSets} = session;
 
     if (!impressionSets) {
       return;
     }
 
     Object.keys(impressionSets).forEach(source => {
-      this.sendEvent(this.createImpressionStats(port, {source, tiles: impressionSets[source]}));
+      const payload = this.createImpressionStats(port, {source, tiles: impressionSets[source]});
+      this.sendEvent(payload);
+      this.sendStructuredIngestionEvent(payload, "impression-stats", "1");
     });
   }
 
   /**
    * handlePagePrerendered - Set the session as prerendered
    *
    * @param  {string} portID the portID of the target session
    */
@@ -551,25 +557,53 @@ this.TelemetryFeed = class TelemetryFeed
   }
 
   sendUTEvent(event_object, eventFunction) {
     if (this.telemetryEnabled && this.eventTelemetryEnabled) {
       eventFunction(event_object);
     }
   }
 
+  /**
+   * Generates an endpoint for Structured Ingestion telemetry pipeline. Note that
+   * Structured Ingestion requires a different endpoint for each ping. See more
+   * details about endpoint schema at:
+   * https://github.com/mozilla/gcp-ingestion/blob/master/docs/edge.md#postput-request
+   *
+   * @param {String} pingType  Type of the ping, such as "impression-stats".
+   * @param {String} version   Endpoint version for this ping type.
+   */
+  _generateStructuredIngestionEndpoint(pingType, version) {
+    const uuid = gUUIDGenerator.generateUUID().toString();
+    // Structured Ingestion does not support the UUID generated by gUUIDGenerator,
+    // because it contains leading and trailing braces. Need to trim them first.
+    const docID = uuid.slice(1, -1);
+    const extension = `${pingType}/${version}/${docID}`;
+    return `${this.structuredIngestionEndpointBase}/${extension}`;
+  }
+
+  sendStructuredIngestionEvent(event_object, pingType, version) {
+    if (this.telemetryEnabled && this.structuredIngestionTelemetryEnabled) {
+      this.pingCentre.sendStructuredIngestionPing(event_object,
+        this._generateStructuredIngestionEndpoint(pingType, version),
+        {filter: ACTIVITY_STREAM_ID});
+    }
+  }
+
   sendASRouterEvent(event_object) {
     if (this.telemetryEnabled) {
       this.pingCentreForASRouter.sendPing(event_object,
       {filter: ACTIVITY_STREAM_ID});
     }
   }
 
   handleImpressionStats(action) {
-    this.sendEvent(this.createImpressionStats(au.getPortIdOfSender(action), action.data));
+    const payload = this.createImpressionStats(au.getPortIdOfSender(action), action.data);
+    this.sendEvent(payload);
+    this.sendStructuredIngestionEvent(payload, "impression-stats", "1");
   }
 
   handleUserEvent(action) {
     let userEvent = this.createUserEvent(action);
     this.sendEvent(userEvent);
     this.sendUTEvent(userEvent, this.utEvents.sendUserEvent);
   }
 
@@ -700,17 +734,17 @@ this.TelemetryFeed = class TelemetryFeed
 
     if (!session) {
       throw new Error("Session does not exist.");
     }
 
     const impressionSets = session.impressionSets || {};
     const impressions = impressionSets[data.source] || [];
     // The payload might contain other properties, we only need `id` here.
-    data.tiles.forEach(tile => impressions.push({id: tile.id}));
+    data.tiles.forEach(tile => impressions.push({id: tile.id, pos: tile.pos}));
     impressionSets[data.source] = impressions;
     session.impressionSets = impressionSets;
   }
 
   /**
    * Take all enumerable members of the data object and merge them into
    * the session.perf object for the given port, so that it is sent to the
    * server when the session ends.  All members of the data object should
@@ -772,25 +806,21 @@ this.TelemetryFeed = class TelemetryFeed
     }
     if (Object.prototype.hasOwnProperty.call(this, "utEvents")) {
       this.utEvents.uninit();
     }
     if (Object.prototype.hasOwnProperty.call(this, "pingCentreForASRouter")) {
       this.pingCentreForASRouter.uninit();
     }
 
-    try {
-      this._prefs.ignore(TELEMETRY_PREF, this._onTelemetryPrefChange);
-      this._prefs.ignore(EVENTS_TELEMETRY_PREF, this._onEventsTelemetryPrefChange);
-    } catch (e) {
-      Cu.reportError(e);
-    }
     // TODO: Send any unfinished sessions
   }
 };
 
 const EXPORTED_SYMBOLS = [
   "TelemetryFeed",
   "USER_PREFS_ENCODING",
   "PREF_IMPRESSION_ID",
   "TELEMETRY_PREF",
   "EVENTS_TELEMETRY_PREF",
+  "STRUCTURED_INGESTION_TELEMETRY_PREF",
+  "STRUCTURED_INGESTION_ENDPOINT_PREF",
 ];
--- a/browser/components/newtab/lib/TopStoriesFeed.jsm
+++ b/browser/components/newtab/lib/TopStoriesFeed.jsm
@@ -57,24 +57,24 @@ this.TopStoriesFeed = class TopStoriesFe
       this.domainAffinitiesLastUpdated = 0;
       this.processAffinityProividerVersion(options);
       this.dispatchPocketCta(this._prefs.get("pocketCta"), false);
       Services.obs.addObserver(this, "idle-daily");
 
       // Cache is used for new page loads, which shouldn't have changed data.
       // If we have changed data, cache should be cleared,
       // and last updated should be 0, and we can fetch.
-      await this.loadCachedData();
+      let {stories, topics} = await this.loadCachedData();
       if (this.storiesLastUpdated === 0) {
-        await this.fetchStories();
+        stories = await this.fetchStories();
       }
       if (this.topicsLastUpdated === 0) {
-        await this.fetchTopics();
+        topics = await this.fetchTopics();
       }
-      this.doContentUpdate(true);
+      this.doContentUpdate({stories, topics}, true);
       this.storiesLoaded = true;
 
       // This is filtered so an update function can return true to retry on the next run
       this.contentUpdateQueue = this.contentUpdateQueue.filter(update => update());
     } catch (e) {
       Cu.reportError(`Problem initializing top stories feed: ${e.message}`);
     }
   }
@@ -108,34 +108,56 @@ this.TopStoriesFeed = class TopStoriesFe
     this.store.dispatch(ac.OnlyToOneContent(action, target));
   }
 
   dispatchPocketCta(data, shouldBroadcast) {
     const action = {type: at.POCKET_CTA, data: JSON.parse(data)};
     this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.AlsoToPreloaded(action));
   }
 
-  doContentUpdate(shouldBroadcast) {
+  /**
+   * doContentUpdate - Updates topics and stories in the topstories section.
+   *
+   *                   Sections have one update action for the whole section.
+   *                   Redux creates a state race condition if you call the same action,
+   *                   twice, concurrently. Because of this, doContentUpdate is
+   *                   one place to update both topics and stories in a single action.
+   *
+   *                   Section updates used old topics if none are available,
+   *                   but clear stories if none are available. Because of this, if no
+   *                   stories are passed, we instead use the existing stories in state.
+   *
+   * @param {Object} This is an object with potential new stories or topics.
+   * @param {Boolean} shouldBroadcast If we should update existing tabs or not. For first page
+   *                  loads or pref changes, we want to update existing tabs,
+   *                  for system tick or other updates we do not.
+   */
+  doContentUpdate({stories, topics}, shouldBroadcast) {
     let updateProps = {};
-    if (this.stories) {
-      updateProps.rows = this.stories;
+    if (stories) {
+      updateProps.rows = stories;
+    } else {
+      const {Sections} = this.store.getState();
+      if (Sections && Sections.find) {
+        updateProps.rows = Sections.find(s => s.id === SECTION_ID).rows;
+      }
     }
-    if (this.topics) {
-      Object.assign(updateProps, {topics: this.topics, read_more_endpoint: this.read_more_endpoint});
+    if (topics) {
+      Object.assign(updateProps, {topics, read_more_endpoint: this.read_more_endpoint});
     }
 
     // We should only be calling this once per init.
     this.dispatchUpdateEvent(shouldBroadcast, updateProps);
   }
 
   async onPersonalityProviderInit() {
     const data = await this.cache.get();
     let stories = data.stories && data.stories.recommendations;
     this.stories = this.rotate(this.transform(stories));
-    this.doContentUpdate(false);
+    this.doContentUpdate({stories: this.stories}, false);
 
     const affinities = this.affinityProvider.getAffinities();
     this.domainAffinitiesLastUpdated = Date.now();
     affinities._timestamp = this.domainAffinitiesLastUpdated;
     this.cache.set("domainAffinities", affinities);
   }
 
   affinityProividerSwitcher(...args) {
@@ -161,17 +183,17 @@ this.TopStoriesFeed = class TopStoriesFe
   }
 
   UserDomainAffinityProvider(...args) {
     return new UserDomainAffinityProvider(...args);
   }
 
   async fetchStories() {
     if (!this.stories_endpoint) {
-      return;
+      return null;
     }
     try {
       const response = await fetch(this.stories_endpoint, {credentials: "omit"});
       if (!response.ok) {
         throw new Error(`Stories endpoint returned unexpected status: ${response.status}`);
       }
 
       const body = await response.json();
@@ -185,16 +207,17 @@ this.TopStoriesFeed = class TopStoriesFe
         this.cleanUpCampaignImpressionPref();
       }
       this.storiesLastUpdated = Date.now();
       body._timestamp = this.storiesLastUpdated;
       this.cache.set("stories", body);
     } catch (error) {
       Cu.reportError(`Failed to fetch content: ${error.message}`);
     }
+    return this.stories;
   }
 
   async loadCachedData() {
     const data = await this.cache.get();
     let stories = data.stories && data.stories.recommendations;
     let topics = data.topics && data.topics.topics;
 
     let affinities = data.domainAffinities;
@@ -212,16 +235,18 @@ this.TopStoriesFeed = class TopStoriesFe
         this.spocs = this.transform(data.stories.spocs).filter(s => s.score >= s.min_score);
         this.cleanUpCampaignImpressionPref();
       }
     }
     if (topics && topics.length > 0 && this.topicsLastUpdated === 0) {
       this.topics = topics;
       this.topicsLastUpdated = data.topics._timestamp;
     }
+
+    return {topics: this.topics, stories: this.stories};
   }
 
   dispatchRelevanceScore(start) {
     let event = "PERSONALIZATION_V1_ITEM_RELEVANCE_SCORE_DURATION";
     let initialized = true;
     if (!this.personalized) {
       return;
     }
@@ -281,17 +306,17 @@ this.TopStoriesFeed = class TopStoriesFe
       .sort(this.personalized ? this.compareScore : (a, b) => 0);
 
     this.dispatchRelevanceScore(scoreStart);
     return calcResult;
   }
 
   async fetchTopics() {
     if (!this.topics_endpoint) {
-      return;
+      return null;
     }
     try {
       const response = await fetch(this.topics_endpoint, {credentials: "omit"});
       if (!response.ok) {
         throw new Error(`Topics endpoint returned unexpected status: ${response.status}`);
       }
       const body = await response.json();
       const {topics} = body;
@@ -299,16 +324,17 @@ this.TopStoriesFeed = class TopStoriesFe
         this.topics = topics;
         this.topicsLastUpdated = Date.now();
         body._timestamp = this.topicsLastUpdated;
         this.cache.set("topics", body);
       }
     } catch (error) {
       Cu.reportError(`Failed to fetch topics: ${error.message}`);
     }
+    return this.topics;
   }
 
   dispatchUpdateEvent(shouldBroadcast, data) {
     SectionsManager.updateSection(SECTION_ID, data, shouldBroadcast);
   }
 
   compareScore(a, b) {
     return b.score - a.score;
@@ -618,24 +644,25 @@ this.TopStoriesFeed = class TopStoriesFe
   }
 
   async onAction(action) {
     switch (action.type) {
       case at.INIT:
         this.init();
         break;
       case at.SYSTEM_TICK:
+        let stories;
+        let topics;
         if (Date.now() - this.storiesLastUpdated >= STORIES_UPDATE_TIME) {
-          await this.fetchStories();
+          stories = await this.fetchStories();
         }
         if (Date.now() - this.topicsLastUpdated >= TOPICS_UPDATE_TIME) {
-          await this.fetchTopics();
+          topics = await this.fetchTopics();
         }
-
-        this.doContentUpdate(false);
+        this.doContentUpdate({stories, topics}, false);
         break;
       case at.UNINIT:
         this.uninit();
         break;
       case at.NEW_TAB_REHYDRATED:
         this.getPocketState(action.meta.fromTarget);
         this.maybeAddSpoc(action.meta.fromTarget);
         break;
--- a/browser/components/newtab/locales-src/bn-BD/strings.properties
+++ b/browser/components/newtab/locales-src/bn-BD/strings.properties
@@ -86,16 +86,19 @@ section_disclaimer_topstories_buttontext=ঠিক আছে, বুঝেছি
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Firefox Home কনটেন্ট
 prefs_home_description=আপনার Firefox Home স্ক্রিনে যেসব কনটেন্ট রাখতে চান তা পছন্দ করুন।
+
+prefs_content_discovery_header=Firefox নীড়
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} সারি; {num} সারিগুলি
 prefs_search_header=ওয়েব অনুসন্ধান
 prefs_topsites_description=যে সাইটগুলিতে আপনি বেশি যান
 prefs_topstories_description2=ওয়েবের দারুন সব কন্টেন্ট, নিজের মত করে সাঁজিয়ে নিন
 prefs_topstories_options_sponsored_label=স্পন্সর করা স্টোরি
--- a/browser/components/newtab/locales-src/de/strings.properties
+++ b/browser/components/newtab/locales-src/de/strings.properties
@@ -86,24 +86,29 @@ section_disclaimer_topstories_buttontext
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Inhalte des Firefox-Startbildschirms
 prefs_home_description=Wählen Sie, welche Inhalte auf Ihrem Firefox-Startbildschirm angezeigt werden sollen.
+
+prefs_content_discovery_header=Firefox-Startseite
+prefs_content_discovery_description="Neues aus dem Netz" macht auf gute Inhalte im Internet aufmerksam.
+prefs_content_discovery_button="Neues aus dem Netz" nicht anzeigen
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} Zeile;{num} Zeilen
 prefs_search_header=Internetsuche
 prefs_topsites_description=Die von die Ihnen am meisten besuchten Websites
 prefs_topstories_description2=Tolle Inhalte aus dem ganzen Internet, für Sie personalisiert
-prefs_topstories_options_sponsored_label=Gesponserte Geschichten
+prefs_topstories_options_sponsored_label=Gesponserte Inhalte
 prefs_topstories_sponsored_learn_more=Weitere Informationen
 prefs_highlights_description=Eine Auswahl von Websites, die Sie gespeichert oder besucht haben
 prefs_highlights_options_visited_label=Besuchte Seiten
 prefs_highlights_options_download_label=Neueste Downloads
 prefs_highlights_options_pocket_label=Bei Pocket gespeicherte Seiten
 prefs_snippets_description=Neuigkeiten von Mozilla und Firefox
 settings_pane_button_label=Einstellungen für neue Tabs anpassen
 settings_pane_topsites_header=Wichtige Seiten
--- a/browser/components/newtab/locales-src/en-GB/strings.properties
+++ b/browser/components/newtab/locales-src/en-GB/strings.properties
@@ -86,16 +86,21 @@ section_disclaimer_topstories_buttontext
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Firefox Home Content
 prefs_home_description=Choose what content you want on your Firefox Home screen.
+
+prefs_content_discovery_header=Firefox Home
+prefs_content_discovery_description=Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.
+prefs_content_discovery_button=Turn Off Content Discovery
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} row;{num} rows
 prefs_search_header=Web Search
 prefs_topsites_description=The sites you visit most
 prefs_topstories_description2=Great content from around the web, personalised for you
 prefs_topstories_options_sponsored_label=Sponsored Stories
--- a/browser/components/newtab/locales-src/fi/strings.properties
+++ b/browser/components/newtab/locales-src/fi/strings.properties
@@ -86,16 +86,21 @@ section_disclaimer_topstories_buttontext=Selvä
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Firefoxin aloitussivun sisältö
 prefs_home_description=Valitse Firefoxin aloitussivulle haluamasi sisältö.
+
+prefs_content_discovery_header=Firefoxin aloitussivu
+prefs_content_discovery_description=Firefoxin aloitussivun sisällön esittely näyttää laadukkaita ja olennaisia artikkeleita ympäri verkkoa.
+prefs_content_discovery_button=Poista sisällön esittely käytöstä
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} rivi;{num} riviä
 prefs_search_header=Verkkohaku
 prefs_topsites_description=Useimmin vierailemasi sivustot
 prefs_topstories_description2=Hyvää sisältöä kaikkialta verkosta, juuri sinulle
 prefs_topstories_options_sponsored_label=Sponsoroidut tarinat
@@ -139,17 +144,16 @@ topsites_form_image_validation=Kuvan lataaminen epäonnistui. Kokeile toista osoitetta.
 
 # LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the
 # trending stories section and precedes a list of links to popular topics.
 pocket_read_more=Suositut aiheet:
 # LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
 # end of the list of popular topic links.
 pocket_read_even_more=Katso lisää juttuja
 pocket_more_reccommendations=Lisää suosituksia
-pocket_learn_more=Lue lisää
 pocket_how_it_works=Kuinka se toimii
 pocket_cta_button=Hanki Pocket
 pocket_cta_text=Tallenna tykkäämäsi tekstit Pocketiin ja ravitse mieltäsi kiinnostavilla teksteillä.
 
 highlights_empty_state=Ala selata, niin tässä alkaa näkyä hyviä juttuja, videoita ja muita sivuja, joilla olet käynyt hiljattain tai jotka olet lisännyt kirjanmerkkeihin.
 # LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
 # in the space that would have shown a few stories, this is shown instead.
 # {provider} is replaced by the name of the content provider for this section.
--- a/browser/components/newtab/locales-src/fr/strings.properties
+++ b/browser/components/newtab/locales-src/fr/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext=J’ai compris
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Contenu de la page d’accueil de Firefox
 prefs_home_description=Choisissez le contenu que vous souhaitez pour la page d’accueil de Firefox.
 
 prefs_content_discovery_header=Page d’accueil de Firefox
+prefs_content_discovery_description=La découverte de contenu dans l’accueil de Firefox vous propose des articles pertinents et de bonne qualité en provenance des quatre coins du Web.
 prefs_content_discovery_button=Désactiver la découverte de contenu
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} ligne;{num} lignes
 prefs_search_header=Recherche web
 prefs_topsites_description=Les sites que vous visitez le plus
--- a/browser/components/newtab/locales-src/gn/strings.properties
+++ b/browser/components/newtab/locales-src/gn/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext=Oĩma, hesakãma chéve
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Kuatiarogue retepy Firefox ñepyrũháme
 prefs_home_description=Eiporavo mba’e retepýpa eipota Firefox mba’erechaha ñepyrũháme.
 
 prefs_content_discovery_header=Firefox kuatiarogue ñepyrũ
+prefs_content_discovery_button=Eipe'a Content Discovery
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} rysýi; {num} rysýi
 prefs_search_header=Ñandutípe jeheka
 prefs_topsites_description=Umi tenda ojeikeveha
 prefs_topstories_description2=Iporãvéva ñanduti retepy, oñemomba’éva ndéve g̃uarã
--- a/browser/components/newtab/locales-src/gu-IN/strings.properties
+++ b/browser/components/newtab/locales-src/gu-IN/strings.properties
@@ -86,16 +86,20 @@ section_disclaimer_topstories_buttontext=ઠીક છે, સમજાઇ ગયું
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Firefox મુખ્ય સામગ્રી
 prefs_home_description=તમારી Firefox મુખ્ય સ્ક્રીન પર કઈ સામગ્રી તમે ઇચ્છો તે પસંદ કરો.
+
+prefs_content_discovery_header=Firefox હોમ
+prefs_content_discovery_button=સામગ્રી ડિસ્કવરી બંધ કરો
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} પંક્તિ;{num} પંક્તિઓ
 prefs_search_header=વેબ શોધ
 prefs_topsites_description=તમે સૌથી વધુ મુલાકાત લો છો તે સાઇટ્સ
 prefs_topstories_description2=તમારા માટે વ્યક્તિગત કરેલ વેબ પરથી, વિખ્યાત સામગ્રી
 prefs_topstories_options_sponsored_label=પ્રાયોજિત વાર્તાઓ
@@ -139,17 +143,16 @@ topsites_form_image_validation=છબી લોડ થવામાં નિષ્ફળ. એક અલગ URL અજમાવી જુઓ.
 
 # LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the
 # trending stories section and precedes a list of links to popular topics.
 pocket_read_more=લોકપ્રિય વિષયો:
 # LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
 # end of the list of popular topic links.
 pocket_read_even_more=વધુ વાર્તાઓ જુઓ
 pocket_more_reccommendations=વધુ ભલામણો
-pocket_learn_more=વધુ શીખો
 pocket_how_it_works=તે કેવી રીતે કામ કરે છે
 pocket_cta_button=Pocket મેળવો
 pocket_cta_text=Pocket તમને જે કથાઓ ગમે છે તે સાચવો, અને તમારા મનને રસપ્રદ વાંચન સાથે ઉત્તેજિત કરો.
 
 highlights_empty_state=બ્રાઉઝ કરવું પ્રારંભ કરો અને અમે અહીં કેટલાક સરસ લેખો, વિડિઓઝ અને અન્ય પૃષ્ઠો દર્શાવીશું જે તમે તાજેતરમાં મુલાકાત લીધાં છે અથવા બુકમાર્ક કર્યા છે.
 # LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
 # in the space that would have shown a few stories, this is shown instead.
 # {provider} is replaced by the name of the content provider for this section.
--- a/browser/components/newtab/locales-src/ja-JP-mac/strings.properties
+++ b/browser/components/newtab/locales-src/ja-JP-mac/strings.properties
@@ -84,18 +84,23 @@ section_disclaimer_topstories_linktext=詳しくはこちら。
 # the button used to acknowledge, and hide this disclaimer in the future.
 section_disclaimer_topstories_buttontext=了解しました
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
-prefs_home_header=Firefox ホームコンテンツ
-prefs_home_description=Firefox のホーム画面に表示するコンテンツを選びましょう。
+prefs_home_header=Firefox Home コンテンツ
+prefs_home_description=Firefox Home に表示するコンテンツを選びましょう。
+
+prefs_content_discovery_header=Firefox Home
+prefs_content_discovery_description=Firefox Home のコンテンツディスカバリーは関連性の高い優れた記事をウェブ上から発見できます。
+prefs_content_discovery_button=コンテンツディスカバリーをオフにする
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} 行
 prefs_search_header=ウェブ検索
 prefs_topsites_description=よく訪れるサイト
 prefs_topstories_description2=ウェブ上の様々な場所から集められた、あなたにピッタリの優れたコンテンツ
 prefs_topstories_options_sponsored_label=広告記事
--- a/browser/components/newtab/locales-src/ja/strings.properties
+++ b/browser/components/newtab/locales-src/ja/strings.properties
@@ -84,18 +84,23 @@ section_disclaimer_topstories_linktext=詳しくはこちら。
 # the button used to acknowledge, and hide this disclaimer in the future.
 section_disclaimer_topstories_buttontext=了解しました
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
-prefs_home_header=Firefox ホームコンテンツ
-prefs_home_description=Firefox のホーム画面に表示するコンテンツを選びましょう。
+prefs_home_header=Firefox Home コンテンツ
+prefs_home_description=Firefox Home に表示するコンテンツを選びましょう。
+
+prefs_content_discovery_header=Firefox Home
+prefs_content_discovery_description=Firefox Home のコンテンツディスカバリーは関連性の高い優れた記事をウェブ上から発見できます。
+prefs_content_discovery_button=コンテンツディスカバリーをオフにする
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} 行
 prefs_search_header=ウェブ検索
 prefs_topsites_description=よく訪れるサイト
 prefs_topstories_description2=ウェブ上の様々な場所から集められた、あなたにピッタリの優れたコンテンツ
 prefs_topstories_options_sponsored_label=広告記事
--- a/browser/components/newtab/locales-src/kk/strings.properties
+++ b/browser/components/newtab/locales-src/kk/strings.properties
@@ -86,16 +86,21 @@ section_disclaimer_topstories_buttontext=Жақсы, түсіндім
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Firefox үй парағы құрамасы
 prefs_home_description=Firefox үй парағында қандай құраманы көргіңіз келетінді таңдаңыз.
+
+prefs_content_discovery_header=Firefox үй парағы
+prefs_content_discovery_description=Firefox үй парағында құраманы табу сізге интернеттен жоғары сапалы, релевантты мақалаларды табуға көмектеседі.
+prefs_content_discovery_button=Құраманы табуды сөндіру
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} жол;{num} жол
 prefs_search_header=Интернеттен іздеу
 prefs_topsites_description=Сіз жиі шолатын сайттар
 prefs_topstories_description2=Бүкіл Интернеттен алынған тамаша контент, талғамыңызға сай таңдалған
 prefs_topstories_options_sponsored_label=Демеушілер мақалалары
@@ -139,17 +144,16 @@ topsites_form_image_validation=Суретті жүктеу қатемен аяқталды. Басқа URL адресін қолданып көріңіз.
 
 # LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the
 # trending stories section and precedes a list of links to popular topics.
 pocket_read_more=Әйгілі тақырыптар:
 # LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
 # end of the list of popular topic links.
 pocket_read_even_more=Көбірек хикаяларды қарау
 pocket_more_reccommendations=Көбірек ұсыныстар
-pocket_learn_more=Көбірек білу
 pocket_how_it_works=Ол қалай жұмыс істейді
 pocket_cta_button=Pocket-ті алу
 pocket_cta_text=Өзіңіз ұнатқан хикаяларды Pocket ішіне сақтап, миіңізді тамаша оқумен толықтырыңыз.
 
 highlights_empty_state=Шолуды бастаңыз, сіз жақында шолған немесе бетбелгілерге қосқан тамаша мақалалар, видеолар немесе басқа парақтардың кейбіреулері осында көрсетіледі.
 # LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
 # in the space that would have shown a few stories, this is shown instead.
 # {provider} is replaced by the name of the content provider for this section.
--- a/browser/components/newtab/locales-src/nn-NO/strings.properties
+++ b/browser/components/newtab/locales-src/nn-NO/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext=OK, eg forstår det!
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Innhald Firefox-startside
 prefs_home_description=Vel kva for innhald du vil ha på Firefox-startsida di.
 
 prefs_content_discovery_header=Firefox startside
+prefs_content_discovery_button=Slå av innhaldsoppdaging
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} rekkje;{num} rekkjer
 prefs_search_header=Nettsøk
 prefs_topsites_description=Sidene du besøkjer mest
 prefs_topstories_description2=Bra innhald frå heile nettet, tilpassa for deg
--- a/browser/components/newtab/locales-src/sk/strings.properties
+++ b/browser/components/newtab/locales-src/sk/strings.properties
@@ -86,16 +86,21 @@ section_disclaimer_topstories_buttontext
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=Obsah domovskej stránky Firefoxu
 prefs_home_description=Vyberte si obsah, ktorý chcete mať na domovskej stránke svojho Firefoxu.
+
+prefs_content_discovery_header=Domovská stránka Firefoxu
+prefs_content_discovery_description=Odporúčanie obsahu na domovskej stránke Firefoxu vám umožňuje objaviť vysokokvalitné a relevantné články z celého internetu.
+prefs_content_discovery_button=Vypnúť odporúčanie obsahu
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} riadok;{num} riadky;{num} riadkov
 prefs_search_header=Vyhľadávanie na webe
 prefs_topsites_description=Najnavštevovanejšie stránky
 prefs_topstories_description2=Skvelý obsah z celého webu, vybraný špeciálne pre vás
 prefs_topstories_options_sponsored_label=Sponzorované stránky
@@ -139,17 +144,16 @@ topsites_form_image_validation=Obrázok sa nepodarilo načítať. Skúste inú URL adresu.
 
 # LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the
 # trending stories section and precedes a list of links to popular topics.
 pocket_read_more=Populárne témy:
 # LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
 # end of the list of popular topic links.
 pocket_read_even_more=Zobraziť ďalšie príbehy
 pocket_more_reccommendations=Ďalšie odporúčania
-pocket_learn_more=Ďalšie informácie
 pocket_how_it_works=Ako to funguje
 pocket_cta_button=Získajte Pocket
 pocket_cta_text=Ukladajte si články do služby Pocket a užívajte si skvelé čítanie.
 
 highlights_empty_state=Začnite s prehliadaním a my vám na tomto mieste ukážeme skvelé články, videá a ostatné stránky, ktoré ste nedávno navštívili alebo pridali medzi záložky.
 # LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
 # in the space that would have shown a few stories, this is shown instead.
 # {provider} is replaced by the name of the content provider for this section.
--- a/browser/components/newtab/locales-src/th/strings.properties
+++ b/browser/components/newtab/locales-src/th/strings.properties
@@ -86,16 +86,21 @@ section_disclaimer_topstories_buttontext=ตกลง เข้าใจแล้ว
 
 # LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
 # for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
 # in English, while "Home" should be localized matching the about:preferences
 # sidebar mozilla-central string for the panel that has preferences related to
 # what is shown for the homepage, new windows, and new tabs.
 prefs_home_header=เนื้อหาหน้าแรก Firefox
 prefs_home_description=เลือกเนื้อหาที่คุณต้องการในหน้าจอหน้าแรก Firefox ของคุณ
+
+prefs_content_discovery_header=Firefox Home
+prefs_content_discovery_description=Content Discovery ใน Firefox Home ช่วยให้คุณค้นพบบทความที่มีคุณภาพและมีความเกี่ยวข้องสูงจากทั่วทั้งเว็บ
+prefs_content_discovery_button=ปิดการค้นพบเนื้อหา
+
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} แถว
 prefs_search_header=การค้นหาเว็บ
 prefs_topsites_description=ไซต์ที่คุณเยี่ยมชมมากที่สุด
 prefs_topstories_description2=เนื้อหาที่ยอดเยี่ยมจากเว็บต่าง ๆ ปรับแต่งให้เป็นส่วนบุคคลเพื่อคุณ
 prefs_topstories_options_sponsored_label=เรื่องราวที่ได้รับการสนับสนุน
@@ -139,18 +144,19 @@ topsites_form_image_validation=ไม่สามารถโหลดภาพ ลอง URL อื่น
 
 # LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the
 # trending stories section and precedes a list of links to popular topics.
 pocket_read_more=หัวข้อยอดนิยม:
 # LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
 # end of the list of popular topic links.
 pocket_read_even_more=ดูเรื่องราวเพิ่มเติม
 pocket_more_reccommendations=คำแนะนำเพิ่มเติม
-pocket_learn_more=เรียนรู้เพิ่มเติม
+pocket_how_it_works=วิธีการทำงาน
 pocket_cta_button=รับ Pocket
+pocket_cta_text=บันทึกเรื่องราวที่คุณรักลงใน Pocket และเติมเต็มสมองของคุณด้วยบทความที่น่าหลงใหล
 
 highlights_empty_state=เริ่มการท่องเว็บและเราจะแสดงบทความ, วิดีโอ และหน้าอื่น ๆ บางส่วนที่ยอดเยี่ยมที่คุณได้เยี่ยมชมหรือเพิ่มที่คั่นหน้าไว้ล่าสุดที่นี่
 # LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
 # in the space that would have shown a few stories, this is shown instead.
 # {provider} is replaced by the name of the content provider for this section.
 topstories_empty_state=คุณได้อ่านเรื่องราวครบทั้งหมดแล้ว คุณสามารถกลับมาตรวจดูเรื่องราวเด่นจาก {provider} ได้ภายหลัง อดใจรอไม่ได้งั้นหรือ? เลือกหัวข้อยอดนิยมเพื่อค้นหาเรื่องราวที่ยอดเยี่ยมจากเว็บต่าง ๆ
 
 # LOCALIZATION NOTE (manual_migration_explanation2): This message is shown to encourage users to
--- a/browser/components/newtab/locales-src/uk/strings.properties
+++ b/browser/components/newtab/locales-src/uk/strings.properties
@@ -95,17 +95,17 @@ prefs_home_description=Оберіть бажаний вміст для показу в домівці Firefox.
 prefs_content_discovery_header=Домівка Firefox
 prefs_content_discovery_description=Огляд вмісту в домівці Firefox дозволяє вам знаходити високоякісні, цікаві статті з усього інтернету.
 prefs_content_discovery_button=Вимкнути огляд вмісту
 
 # LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
 # plural forms used in a drop down of multiple row options (1 row, 2 rows).
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 prefs_section_rows_option={num} рядок;{num} рядки;{num} рядків
-prefs_search_header=Веб пошук
+prefs_search_header=Пошук в Інтернеті
 prefs_topsites_description=Сайти, які ви відвідуєте найчастіше
 prefs_topstories_description2=Чудові матеріали з усього інтернету, відібрані спеціально для вас
 prefs_topstories_options_sponsored_label=Матеріали від спонсорів
 prefs_topstories_sponsored_learn_more=Докладніше
 prefs_highlights_description=Відібрані веб-сайти, які ви зберегли чи відвідали
 prefs_highlights_options_visited_label=Відвідані сторінки
 prefs_highlights_options_download_label=Останні завантаження
 prefs_highlights_options_pocket_label=Збережене в Pocket
--- a/browser/components/newtab/package-lock.json
+++ b/browser/components/newtab/package-lock.json
@@ -785,21 +785,16 @@
       "dev": true
     },
     "arrify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
       "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
       "dev": true
     },
-    "asap": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
-      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
-    },
     "asn1": {
       "version": "0.2.4",
       "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
       "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
       "dev": true,
       "requires": {
         "safer-buffer": "~2.1.0"
       }
@@ -2236,21 +2231,16 @@
       }
     },
     "copy-descriptor": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
       "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
       "dev": true
     },
-    "core-js": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
-      "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
-    },
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
       "dev": true
     },
     "cosmiconfig": {
       "version": "5.0.7",
@@ -2763,24 +2753,16 @@
       "dev": true
     },
     "encodeurl": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
       "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
       "dev": true
     },
-    "encoding": {
-      "version": "0.1.12",
-      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
-      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
-      "requires": {
-        "iconv-lite": "~0.4.13"
-      }
-    },
     "end-of-stream": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
       "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
       "dev": true,
       "requires": {
         "once": "^1.4.0"
       }
@@ -3570,16 +3552,22 @@
           "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
           "dev": true,
           "requires": {
             "function-bind": "^1.1.1"
           }
         }
       }
     },
+    "eslint-plugin-react-hooks": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.3.0.tgz",
+      "integrity": "sha512-hjgyNq0sfDXaLkXHkmo3vRh+p+42lwQIU3r56hVoomMYRMToJ2D/PGhwL2EPyB5ZEMlbLsRm3s5v4gm5FIjlvg==",
+      "dev": true
+    },
     "eslint-scope": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz",
       "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==",
       "dev": true,
       "requires": {
         "esrecurse": "^4.1.0",
         "estraverse": "^4.1.1"
@@ -4378,30 +4366,16 @@
       "dev": true
     },
     "fast-levenshtein": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
       "dev": true
     },
-    "fbjs": {
-      "version": "0.8.16",
-      "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
-      "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=",
-      "requires": {
-        "core-js": "^1.0.0",
-        "isomorphic-fetch": "^2.1.1",
-        "loose-envify": "^1.0.0",
-        "object-assign": "^4.1.0",
-        "promise": "^7.1.1",
-        "setimmediate": "^1.0.5",
-        "ua-parser-js": "^0.7.9"
-      }
-    },
     "fd-slicer": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
       "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
       "dev": true,
       "optional": true,
       "requires": {
         "pend": "~1.2.0"
@@ -5930,21 +5904,16 @@
         "slash": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
           "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
           "dev": true
         }
       }
     },
-    "iconv-lite": {
-      "version": "0.4.19",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-      "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
-    },
     "ieee754": {
       "version": "1.1.12",
       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
       "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==",
       "dev": true
     },
     "iferr": {
       "version": "0.1.5",
@@ -6487,17 +6456,18 @@
       "dev": true,
       "requires": {
         "tryit": "^1.0.1"
       }
     },
     "is-stream": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
     },
     "is-string": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz",
       "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=",
       "dev": true
     },
     "is-subset": {
@@ -6555,36 +6525,16 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
       "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
       "dev": true,
       "requires": {
         "isarray": "1.0.0"
       }
     },
-    "isomorphic-fetch": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
-      "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
-      "requires": {
-        "node-fetch": "^1.0.1",
-        "whatwg-fetch": ">=0.10.0"
-      },
-      "dependencies": {
-        "node-fetch": {
-          "version": "1.7.3",
-          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
-          "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
-          "requires": {
-            "encoding": "^0.1.11",
-            "is-stream": "^1.0.1"
-          }
-        }
-      }
-    },
     "isstream": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
       "dev": true
     },
     "istanbul-api": {
       "version": "2.0.6",
@@ -9572,24 +9522,16 @@
       "dev": true
     },
     "progress": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz",
       "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==",
       "dev": true
     },
-    "promise": {
-      "version": "7.3.1",
-      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
-      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
-      "requires": {
-        "asap": "~2.0.3"
-      }
-    },
     "promise-inflight": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
       "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
       "dev": true
     },
     "prop-types": {
       "version": "15.6.2",
@@ -9820,35 +9762,35 @@
     },
     "raw-loader": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz",
       "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=",
       "dev": true
     },
     "react": {
-      "version": "16.2.0",
-      "resolved": "https://registry.npmjs.org/react/-/react-16.2.0.tgz",
-      "integrity": "sha512-ZmIomM7EE1DvPEnSFAHZn9Vs9zJl5A9H7el0EGTE6ZbW9FKe/14IYAlPbC8iH25YarEQxZL+E8VW7Mi7kfQrDQ==",
-      "requires": {
-        "fbjs": "^0.8.16",
+      "version": "16.8.3",
+      "resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz",
+      "integrity": "sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==",
+      "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1",
-        "prop-types": "^15.6.0"
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.13.3"
       }
     },
     "react-dom": {
-      "version": "16.2.0",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.2.0.tgz",
-      "integrity": "sha512-zpGAdwHVn9K0091d+hr+R0qrjoJ84cIBFL2uU60KvWBPfZ7LPSrfqviTxGHWN0sjPZb2hxWzMexwrvJdKePvjg==",
-      "requires": {
-        "fbjs": "^0.8.16",
+      "version": "16.8.3",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.3.tgz",
+      "integrity": "sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==",
+      "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1",
-        "prop-types": "^15.6.0"
+        "prop-types": "^15.6.2",
+        "scheduler": "^0.13.3"
       }
     },
     "react-intl": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.4.0.tgz",
       "integrity": "sha1-ZsFNyd+ac7L7v71gIXJugKYT6xU=",
       "requires": {
         "intl-format-cache": "^2.0.5",
@@ -9892,31 +9834,31 @@
         "react-is": {
           "version": "16.6.0",
           "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz",
           "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g=="
         }
       }
     },
     "react-test-renderer": {
-      "version": "16.6.1",
-      "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.1.tgz",
-      "integrity": "sha512-sgZwJZYIgQptRi2qk5+gB8FBQGk4gLSs0gmKZPMfhd3dLkdxIUwVLHteLN3Bnj4LokIZd3U+V2NEJUqeV2PT2w==",
+      "version": "16.8.3",
+      "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.3.tgz",
+      "integrity": "sha512-rjJGYebduKNZH0k1bUivVrRLX04JfIQ0FKJLPK10TAb06XWhfi4gTobooF9K/DEFNW98iGac3OSxkfIJUN9Mdg==",
       "dev": true,
       "requires": {
         "object-assign": "^4.1.1",
         "prop-types": "^15.6.2",
-        "react-is": "^16.6.1",
-        "scheduler": "^0.11.0"
+        "react-is": "^16.8.3",
+        "scheduler": "^0.13.3"
       },
       "dependencies": {
         "react-is": {
-          "version": "16.6.1",
-          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.1.tgz",
-          "integrity": "sha512-wOKsGtvTMYs7WAscmwwdM8sfRRvE17Ym30zFj3n37Qx5tHRfhenPKEPILHaHob6WoLFADmQm1ZNrE5xMCM6sCw==",
+          "version": "16.8.3",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz",
+          "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==",
           "dev": true
         }
       }
     },
     "read-pkg": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
       "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
@@ -10990,20 +10932,19 @@
     },
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
       "dev": true
     },
     "scheduler": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.0.tgz",
-      "integrity": "sha512-MAYbBfmiEHxF0W+c4CxMpEqMYK+rYF584VP/qMKSiHM6lTkBKKYOJaDiSILpJHla6hBOsVd6GucPL46o2Uq3sg==",
-      "dev": true,
+      "version": "0.13.3",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz",
+      "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==",
       "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1"
       }
     },
     "schema-utils": {
       "version": "0.4.7",
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
@@ -11118,17 +11059,18 @@
             "is-extendable": "^0.1.0"
           }
         }
       }
     },
     "setimmediate": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
+      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+      "dev": true
     },
     "setprototypeof": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
       "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
       "dev": true
     },
     "sha.js": {
@@ -12155,21 +12097,16 @@
       }
     },
     "typedarray": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
       "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
       "dev": true
     },
-    "ua-parser-js": {
-      "version": "0.7.17",
-      "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
-      "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g=="
-    },
     "uglify-es": {
       "version": "3.3.9",
       "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
       "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
       "dev": true,
       "requires": {
         "commander": "~2.13.0",
         "source-map": "~0.6.1"
@@ -13591,21 +13528,16 @@
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
           "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
           "dev": true
         }
       }
     },
-    "whatwg-fetch": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
-      "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
-    },
     "which": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
       "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
       "dev": true,
       "requires": {
         "isexe": "^2.0.0"
       }
--- a/browser/components/newtab/package.json
+++ b/browser/components/newtab/package.json
@@ -4,18 +4,18 @@
   "version": "1.14.3",
   "author": "Mozilla (https://mozilla.org/)",
   "bugs": {
     "url": "https://github.com/mozilla/activity-stream/issues"
   },
   "dependencies": {
     "fluent": "0.6.4",
     "fluent-react": "0.7.0",
-    "react": "16.2.0",
-    "react-dom": "16.2.0",
+    "react": "16.8.3",
+    "react-dom": "16.8.3",
     "react-intl": "2.4.0",
     "react-redux": "5.1.0",
     "redux": "4.0.1",
     "reselect": "4.0.0"
   },
   "devDependencies": {
     "@octokit/rest": "15.17.0",
     "acorn": "6.0.4",
@@ -38,16 +38,17 @@
     "enzyme-adapter-react-16": "1.7.0",
     "eslint": "5.9.0",
     "eslint-plugin-import": "2.14.0",
     "eslint-plugin-json": "1.2.1",
     "eslint-plugin-mozilla": "1.1.0",
     "eslint-plugin-no-unsanitized": "3.0.2",
     "eslint-plugin-promise": "4.0.1",
     "eslint-plugin-react": "7.11.1",
+    "eslint-plugin-react-hooks": "1.3.0",
     "eslint-watch": "4.0.2",
     "husky": "1.1.3",
     "istanbul-instrumenter-loader": "3.0.1",
     "joi-browser": "13.4.0",
     "karma": "3.1.1",
     "karma-chai": "0.1.0",
     "karma-coverage-istanbul-reporter": "2.0.4",
     "karma-firefox-launcher": "1.1.0",
@@ -61,17 +62,17 @@
     "mocha": "5.2.0",
     "mock-raf": "1.0.1",
     "node-fetch": "2.2.1",
     "node-sass": "4.10.0",
     "npm-run-all": "4.1.5",
     "pontoon-to-json": "2.0.0",
     "prop-types": "15.6.2",
     "raw-loader": "0.5.1",
-    "react-test-renderer": "16.6.1",
+    "react-test-renderer": "16.8.3",
     "rimraf": "2.6.2",
     "sass": "1.14.3",
     "sass-lint": "1.12.1",
     "shelljs": "0.8.2",
     "simple-git": "1.107.0",
     "sinon": "7.1.1",
     "webpack": "4.25.1",
     "webpack-cli": "3.1.2",
@@ -136,16 +137,18 @@
     "debugcoverage": "open logs/coverage/index.html",
     "lint": "npm-run-all lint:*",
     "lint:eslint": "esw --ext=.js,.jsm,.json,.jsx .",
     "lint:sasslint": "sass-lint -v -q",
     "strings-import": "node ./bin/strings-import.js",
     "test": "npm run testmc",
     "tdd": "npm run tddmc",
     "prepush": "npm run lint && npm run yamscripts",
+    "vendor": "npm-run-all vendor:*",
+    "vendor:react": "node ./bin/vendor-react.js",
     "help": "yamscripts help",
     "yamscripts": "yamscripts compile",
     "__": "# NOTE: THESE SCRIPTS ARE COMPILED!!! EDIT yamscripts.yml instead!!!"
   },
   "title": "Activity Stream",
   "permissions": {
     "multiprocess": true,
     "private-browsing": true
--- a/browser/components/newtab/prerendered/locales/bn-BD/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/bn-BD/activity-stream-strings.js
@@ -35,17 +35,17 @@ window.gActivityStreamStrings = {
   "search_button": "অনুসন্ধান",
   "search_header": "{search_engine_name} খুঁজুন",
   "search_web_placeholder": "ওয়েবে সন্ধান করুন",
   "section_disclaimer_topstories": "মজার মজার সব গল্প নির্বাচিত হয়েছে, আপনি যেমনটা পড়েন। Pocket এখন থেকে Mozilla এর অংশ।",
   "section_disclaimer_topstories_linktext": "কিভাবে কাজ করে জানুন।",
   "section_disclaimer_topstories_buttontext": "ঠিক আছে, বুঝেছি",
   "prefs_home_header": "Firefox Home কনটেন্ট",
   "prefs_home_description": "আপনার Firefox Home স্ক্রিনে যেসব কনটেন্ট রাখতে চান তা পছন্দ করুন।",
-  "prefs_content_discovery_header": "Firefox Home",
+  "prefs_content_discovery_header": "Firefox নীড়",
   "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
   "prefs_content_discovery_button": "Turn Off Content Discovery",
   "prefs_section_rows_option": "{num} সারি; {num} সারিগুলি",
   "prefs_search_header": "ওয়েব অনুসন্ধান",
   "prefs_topsites_description": "যে সাইটগুলিতে আপনি বেশি যান",
   "prefs_topstories_description2": "ওয়েবের দারুন সব কন্টেন্ট, নিজের মত করে সাঁজিয়ে নিন",
   "prefs_topstories_options_sponsored_label": "স্পন্সর করা স্টোরি",
   "prefs_topstories_sponsored_learn_more": "আরও জানুন",
--- a/browser/components/newtab/prerendered/locales/bn-IN/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/bn-IN/activity-stream-strings.js
@@ -35,17 +35,17 @@ window.gActivityStreamStrings = {
   "search_button": "অনুসন্ধান",
   "search_header": "{search_engine_name} এ অনুসন্ধান করুন",
   "search_web_placeholder": "ওয়েবে সন্ধান করুন",
   "section_disclaimer_topstories": "মজার মজার সব গল্প নির্বাচিত হয়েছে, আপনি যেমনটা পড়েন। Pocket এখন থেকে Mozilla -র অংশ।",
   "section_disclaimer_topstories_linktext": "কিভাবে কাজ করে জানুন।",
   "section_disclaimer_topstories_buttontext": "ঠিক আছে, বুঝেছি",
   "prefs_home_header": "Firefox হোমের বিষয়বস্তু",
   "prefs_home_description": "আপনার Firefox হোম স্ক্রিনে যেসব বিষয়বস্তু রাখতে চান তা পছন্দ করুন।",
-  "prefs_content_discovery_header": "Firefox Home",
+  "prefs_content_discovery_header": "Firefox নীড়",
   "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
   "prefs_content_discovery_button": "Turn Off Content Discovery",
   "prefs_section_rows_option": "{num} টি সারি; {num} টি সারি",
   "prefs_search_header": "ওয়েবে অনুসন্ধান",
   "prefs_topsites_description": "যে সাইটগুলিতে আপনি বেশি যান",
   "prefs_topstories_description2": "ওয়েবের দারুন সব বিষয়বস্তু, নিজের মত করে সাজিয়ে নিন",
   "prefs_topstories_options_sponsored_label": "স্পন্সর করা গল্প",
   "prefs_topstories_sponsored_learn_more": "আরো জানুন",
--- a/browser/components/newtab/prerendered/locales/de/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/de/activity-stream-strings.js
@@ -35,24 +35,24 @@ window.gActivityStreamStrings = {
   "search_button": "Suchen",
   "search_header": "{search_engine_name}-Suche",
   "search_web_placeholder": "Das Web durchsuchen",
   "section_disclaimer_topstories": "Die interessanten Geschichten im Internet, ausgewählt nach Ihrem Geschmack. Von Pocket, jetzt Teil von Mozilla.",
   "section_disclaimer_topstories_linktext": "Erfahren Sie, wie es funktioniert.",
   "section_disclaimer_topstories_buttontext": "Ok, verstanden",
   "prefs_home_header": "Inhalte des Firefox-Startbildschirms",
   "prefs_home_description": "Wählen Sie, welche Inhalte auf Ihrem Firefox-Startbildschirm angezeigt werden sollen.",
-  "prefs_content_discovery_header": "Firefox Home",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_header": "Firefox-Startseite",
+  "prefs_content_discovery_description": "\"Neues aus dem Netz\" macht auf gute Inhalte im Internet aufmerksam.",
+  "prefs_content_discovery_button": "\"Neues aus dem Netz\" nicht anzeigen",
   "prefs_section_rows_option": "{num} Zeile;{num} Zeilen",
   "prefs_search_header": "Internetsuche",
   "prefs_topsites_description": "Die von die Ihnen am meisten besuchten Websites",
   "prefs_topstories_description2": "Tolle Inhalte aus dem ganzen Internet, für Sie personalisiert",
-  "prefs_topstories_options_sponsored_label": "Gesponserte Geschichten",
+  "prefs_topstories_options_sponsored_label": "Gesponserte Inhalte",
   "prefs_topstories_sponsored_learn_more": "Weitere Informationen",
   "prefs_highlights_description": "Eine Auswahl von Websites, die Sie gespeichert oder besucht haben",
   "prefs_highlights_options_visited_label": "Besuchte Seiten",
   "prefs_highlights_options_download_label": "Neueste Downloads",
   "prefs_highlights_options_pocket_label": "Bei Pocket gespeicherte Seiten",
   "prefs_snippets_description": "Neuigkeiten von Mozilla und Firefox",
   "settings_pane_button_label": "Einstellungen für neue Tabs anpassen",
   "settings_pane_topsites_header": "Wichtige Seiten",
--- a/browser/components/newtab/prerendered/locales/fi/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/fi/activity-stream-strings.js
@@ -35,19 +35,19 @@ window.gActivityStreamStrings = {
   "search_button": "Haku",
   "search_header": "{search_engine_name}-haku",
   "search_web_placeholder": "Verkkohaku",
   "section_disclaimer_topstories": "Verkon kiinnostavimmat jutut, lukemasi perusteella valittuna. Pocketilta, joka on nyt osa Mozillaa.",
   "section_disclaimer_topstories_linktext": "Lue, miten tämä toimii.",
   "section_disclaimer_topstories_buttontext": "Selvä",
   "prefs_home_header": "Firefoxin aloitussivun sisältö",
   "prefs_home_description": "Valitse Firefoxin aloitussivulle haluamasi sisältö.",
-  "prefs_content_discovery_header": "Firefox Home",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_header": "Firefoxin aloitussivu",
+  "prefs_content_discovery_description": "Firefoxin aloitussivun sisällön esittely näyttää laadukkaita ja olennaisia artikkeleita ympäri verkkoa.",
+  "prefs_content_discovery_button": "Poista sisällön esittely käytöstä",
   "prefs_section_rows_option": "{num} rivi;{num} riviä",
   "prefs_search_header": "Verkkohaku",
   "prefs_topsites_description": "Useimmin vierailemasi sivustot",
   "prefs_topstories_description2": "Hyvää sisältöä kaikkialta verkosta, juuri sinulle",
   "prefs_topstories_options_sponsored_label": "Sponsoroidut tarinat",
   "prefs_topstories_sponsored_learn_more": "Lue lisää",
   "prefs_highlights_description": "Valikoima sivustoja, joilla olet käynyt tai jotka olet tallentanut",
   "prefs_highlights_options_visited_label": "Vieraillut sivustot",
@@ -105,11 +105,10 @@ window.gActivityStreamStrings = {
   "firstrun_form_sub_header": "jatkaaksesi Firefox Sync -palveluun.",
   "firstrun_email_input_placeholder": "Sähköposti",
   "firstrun_invalid_input": "Sähköpostiosoitteen täytyy olla kelvollinen",
   "firstrun_extra_legal_links": "Jatkamalla hyväksyt {terms} ja {privacy}.",
   "firstrun_terms_of_service": "käyttöehdot",
   "firstrun_privacy_notice": "tietosuojakäytännön",
   "firstrun_continue_to_login": "Jatka",
   "firstrun_skip_login": "Ohita tämä vaihe",
-  "context_menu_title": "Avaa valikko",
-  "pocket_learn_more": "Lue lisää"
+  "context_menu_title": "Avaa valikko"
 };
--- a/browser/components/newtab/prerendered/locales/fr/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/fr/activity-stream-strings.js
@@ -36,17 +36,17 @@ window.gActivityStreamStrings = {
   "search_header": "Recherche {search_engine_name}",
   "search_web_placeholder": "Rechercher sur le Web",
   "section_disclaimer_topstories": "Les articles les plus intéressants du Web, sélectionnés selon ce que vous lisez. Et ceci grâce à Pocket, qui fait désormais partie de Mozilla.",
   "section_disclaimer_topstories_linktext": "Découvrez comment cela fonctionne.",
   "section_disclaimer_topstories_buttontext": "J’ai compris",
   "prefs_home_header": "Contenu de la page d’accueil de Firefox",
   "prefs_home_description": "Choisissez le contenu que vous souhaitez pour la page d’accueil de Firefox.",
   "prefs_content_discovery_header": "Page d’accueil de Firefox",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
+  "prefs_content_discovery_description": "La découverte de contenu dans l’accueil de Firefox vous propose des articles pertinents et de bonne qualité en provenance des quatre coins du Web.",
   "prefs_content_discovery_button": "Désactiver la découverte de contenu",
   "prefs_section_rows_option": "{num} ligne;{num} lignes",
   "prefs_search_header": "Recherche web",
   "prefs_topsites_description": "Les sites que vous visitez le plus",
   "prefs_topstories_description2": "Du contenu intéressant en provenance du Web, personnalisé pour vous",
   "prefs_topstories_options_sponsored_label": "Articles sponsorisés",
   "prefs_topstories_sponsored_learn_more": "En savoir plus",
   "prefs_highlights_description": "Une sélection de sites que vous avez sauvegardés ou visités",
--- a/browser/components/newtab/prerendered/locales/gn/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/gn/activity-stream-strings.js
@@ -37,17 +37,17 @@ window.gActivityStreamStrings = {
   "search_web_placeholder": "Ñandutivevépe Jeheka",
   "section_disclaimer_topstories": "Mba'erechapyrã ñandutivevepegua ojeiporavóva ndéve g̃uarã ojejesareko rupi remoñe'ẽva jepi rehe. Pocket guive ha'éva ko'ág̃a Mozilla mba'e.",
   "section_disclaimer_topstories_linktext": "Eikuaave mba'éichapa oiko.",
   "section_disclaimer_topstories_buttontext": "Oĩma, hesakãma chéve",
   "prefs_home_header": "Kuatiarogue retepy Firefox ñepyrũháme",
   "prefs_home_description": "Eiporavo mba’e retepýpa eipota Firefox mba’erechaha ñepyrũháme.",
   "prefs_content_discovery_header": "Firefox kuatiarogue ñepyrũ",
   "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_button": "Eipe'a Content Discovery",
   "prefs_section_rows_option": "{num} rysýi; {num} rysýi",
   "prefs_search_header": "Ñandutípe jeheka",
   "prefs_topsites_description": "Umi tenda ojeikeveha",
   "prefs_topstories_description2": "Iporãvéva ñanduti retepy, oñemomba’éva ndéve g̃uarã",
   "prefs_topstories_options_sponsored_label": "Tembiasakue jehepyme'ẽguáva",
   "prefs_topstories_sponsored_learn_more": "Kuaave",
   "prefs_highlights_description": "Tenda jeporavopy eñongatu térã eike hague",
   "prefs_highlights_options_visited_label": "Tenda jeikepyre",
--- a/browser/components/newtab/prerendered/locales/gu-IN/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/gu-IN/activity-stream-strings.js
@@ -35,19 +35,19 @@ window.gActivityStreamStrings = {
   "search_button": "શોધો",
   "search_header": "{search_engine_name} શોધ કરો",
   "search_web_placeholder": "વેબ પર શોધો",
   "section_disclaimer_topstories": "વેબ પરની સૌથી રસપ્રદ વાર્તાઓ, તમે જે વાંચો છો તેના આધારે પસંદ કરેલ છે. Pocket થી, હવે Mozilla નો ભાગ.",
   "section_disclaimer_topstories_linktext": "તે કેવી રીતે કાર્ય કરે છે તે જાણો.",
   "section_disclaimer_topstories_buttontext": "ઠીક છે, સમજાઇ ગયું",
   "prefs_home_header": "Firefox મુખ્ય સામગ્રી",
   "prefs_home_description": "તમારી Firefox મુખ્ય સ્ક્રીન પર કઈ સામગ્રી તમે ઇચ્છો તે પસંદ કરો.",
-  "prefs_content_discovery_header": "Firefox Home",
+  "prefs_content_discovery_header": "Firefox હોમ",
   "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_button": "સામગ્રી ડિસ્કવરી બંધ કરો",
   "prefs_section_rows_option": "{num} પંક્તિ;{num} પંક્તિઓ",
   "prefs_search_header": "વેબ શોધ",
   "prefs_topsites_description": "તમે સૌથી વધુ મુલાકાત લો છો તે સાઇટ્સ",
   "prefs_topstories_description2": "તમારા માટે વ્યક્તિગત કરેલ વેબ પરથી, વિખ્યાત સામગ્રી",
   "prefs_topstories_options_sponsored_label": "પ્રાયોજિત વાર્તાઓ",
   "prefs_topstories_sponsored_learn_more": "વધુ શીખો",
   "prefs_highlights_description": "સાઇટ્સની પસંદગી કે જે તમે સાચવી અથવા મુલાકાત લીધી છે",
   "prefs_highlights_options_visited_label": "મુલાકાત લીધેલા પૃષ્ઠો",
@@ -105,11 +105,10 @@ window.gActivityStreamStrings = {
   "firstrun_form_sub_header": "Firefox સમન્વયન ચાલુ રાખવા માટે.",
   "firstrun_email_input_placeholder": "ઇમેઇલ",
   "firstrun_invalid_input": "માન્ય ઇમેઇલ આવશ્યક છે",
   "firstrun_extra_legal_links": "આગળ વધીને, તમે {terms} અને {privacy} સાથે સંમત થાઓ છો.",
   "firstrun_terms_of_service": "સેવાની શરતો",
   "firstrun_privacy_notice": "ખાનગી સૂચના",
   "firstrun_continue_to_login": "ચાલુ રાખો",
   "firstrun_skip_login": "આ પગલું છોડી દો",
-  "context_menu_title": "મેનૂ ખોલો",
-  "pocket_learn_more": "વધુ શીખો"
+  "context_menu_title": "મેનૂ ખોલો"
 };
--- a/browser/components/newtab/prerendered/locales/ja-JP-mac/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/ja-JP-mac/activity-stream-strings.js
@@ -33,21 +33,21 @@ window.gActivityStreamStrings = {
   "menu_action_go_to_download_page": "ダウンロード元のページを開く",
   "menu_action_remove_download": "履歴から削除",
   "search_button": "検索",
   "search_header": "{search_engine_name} 検索",
   "search_web_placeholder": "ウェブを検索",
   "section_disclaimer_topstories": "あなたが読んだページに基づいて選ばれた、ウェブ上で最も興味深い記事。Mozilla の一員となった Pocket がお届けします。",
   "section_disclaimer_topstories_linktext": "詳しくはこちら。",
   "section_disclaimer_topstories_buttontext": "了解しました",
-  "prefs_home_header": "Firefox ホームコンテンツ",
-  "prefs_home_description": "Firefox のホーム画面に表示するコンテンツを選びましょう。",
+  "prefs_home_header": "Firefox Home コンテンツ",
+  "prefs_home_description": "Firefox Home に表示するコンテンツを選びましょう。",
   "prefs_content_discovery_header": "Firefox Home",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_description": "Firefox Home のコンテンツディスカバリーは関連性の高い優れた記事をウェブ上から発見できます。",
+  "prefs_content_discovery_button": "コンテンツディスカバリーをオフにする",
   "prefs_section_rows_option": "{num} 行",
   "prefs_search_header": "ウェブ検索",
   "prefs_topsites_description": "よく訪れるサイト",
   "prefs_topstories_description2": "ウェブ上の様々な場所から集められた、あなたにピッタリの優れたコンテンツ",
   "prefs_topstories_options_sponsored_label": "広告記事",
   "prefs_topstories_sponsored_learn_more": "詳しくはこちら",
   "prefs_highlights_description": "保存したり訪れたりしたサイトうち主なもの",
   "prefs_highlights_options_visited_label": "訪れたページ",
--- a/browser/components/newtab/prerendered/locales/ja/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/ja/activity-stream-strings.js
@@ -33,21 +33,21 @@ window.gActivityStreamStrings = {
   "menu_action_go_to_download_page": "ダウンロード元のページを開く",
   "menu_action_remove_download": "履歴から削除",
   "search_button": "検索",
   "search_header": "{search_engine_name} 検索",
   "search_web_placeholder": "ウェブを検索",
   "section_disclaimer_topstories": "あなたが読んだページに基づいて選ばれた、ウェブ上で最も興味深い記事。Mozilla の一員となった Pocket がお届けします。",
   "section_disclaimer_topstories_linktext": "詳しくはこちら。",
   "section_disclaimer_topstories_buttontext": "了解しました",
-  "prefs_home_header": "Firefox ホームコンテンツ",
-  "prefs_home_description": "Firefox のホーム画面に表示するコンテンツを選びましょう。",
+  "prefs_home_header": "Firefox Home コンテンツ",
+  "prefs_home_description": "Firefox Home に表示するコンテンツを選びましょう。",
   "prefs_content_discovery_header": "Firefox Home",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_description": "Firefox Home のコンテンツディスカバリーは関連性の高い優れた記事をウェブ上から発見できます。",
+  "prefs_content_discovery_button": "コンテンツディスカバリーをオフにする",
   "prefs_section_rows_option": "{num} 行",
   "prefs_search_header": "ウェブ検索",
   "prefs_topsites_description": "よく訪れるサイト",
   "prefs_topstories_description2": "ウェブ上の様々な場所から集められた、あなたにピッタリの優れたコンテンツ",
   "prefs_topstories_options_sponsored_label": "広告記事",
   "prefs_topstories_sponsored_learn_more": "詳しくはこちら",
   "prefs_highlights_description": "保存したり訪れたりしたサイトうち主なもの",
   "prefs_highlights_options_visited_label": "訪れたページ",
--- a/browser/components/newtab/prerendered/locales/kk/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/kk/activity-stream-strings.js
@@ -35,19 +35,19 @@ window.gActivityStreamStrings = {
   "search_button": "Іздеу",
   "search_header": "{search_engine_name} іздеуі",
   "search_web_placeholder": "Интернетте іздеу",
   "section_disclaimer_topstories": "Сіз оқитын нәрселерге негізделген интернеттегі ең қызықты хикаялар. Mozilla құрамындағы Pocket ұсынады.",
   "section_disclaimer_topstories_linktext": "Бұл қалай жұмыс жасайтынын білу.",
   "section_disclaimer_topstories_buttontext": "Жақсы, түсіндім",
   "prefs_home_header": "Firefox үй парағы құрамасы",
   "prefs_home_description": "Firefox үй парағында қандай құраманы көргіңіз келетінді таңдаңыз.",
-  "prefs_content_discovery_header": "Firefox Home",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_header": "Firefox үй парағы",
+  "prefs_content_discovery_description": "Firefox үй парағында құраманы табу сізге интернеттен жоғары сапалы, релевантты мақалаларды табуға көмектеседі.",
+  "prefs_content_discovery_button": "Құраманы табуды сөндіру",
   "prefs_section_rows_option": "{num} жол;{num} жол",
   "prefs_search_header": "Интернеттен іздеу",
   "prefs_topsites_description": "Сіз жиі шолатын сайттар",
   "prefs_topstories_description2": "Бүкіл Интернеттен алынған тамаша контент, талғамыңызға сай таңдалған",
   "prefs_topstories_options_sponsored_label": "Демеушілер мақалалары",
   "prefs_topstories_sponsored_learn_more": "Көбірек білу",
   "prefs_highlights_description": "Сіз сақтаған немесе шолған таңдамалы сайттар",
   "prefs_highlights_options_visited_label": "Қаралған беттер",
@@ -105,11 +105,10 @@ window.gActivityStreamStrings = {
   "firstrun_form_sub_header": "Firefox синхрондауына жалғастыру үшін.",
   "firstrun_email_input_placeholder": "Эл. пошта",
   "firstrun_invalid_input": "Жарамды эл. пошта адресі керек",
   "firstrun_extra_legal_links": "Жалғастырсаңыз, {terms} және {privacy} шарттарымен келісесіз.",
   "firstrun_terms_of_service": "Қолдану шарттары",
   "firstrun_privacy_notice": "Жекелік ескертуі",
   "firstrun_continue_to_login": "Жалғастыру",
   "firstrun_skip_login": "Бұл қадамды аттап кету",
-  "context_menu_title": "Мәзірді ашу",
-  "pocket_learn_more": "Көбірек білу"
+  "context_menu_title": "Мәзірді ашу"
 };
--- a/browser/components/newtab/prerendered/locales/nn-NO/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/nn-NO/activity-stream-strings.js
@@ -37,17 +37,17 @@ window.gActivityStreamStrings = {
   "search_web_placeholder": "Søk på nettet",
   "section_disclaimer_topstories": "Dei mest interessante historiane på nettet, utvalde basert på kva du les. Frå Pocket, no ein del av Mozilla.",
   "section_disclaimer_topstories_linktext": "Sjå korleis det fungerer.",
   "section_disclaimer_topstories_buttontext": "OK, eg forstår det!",
   "prefs_home_header": "Innhald Firefox-startside",
   "prefs_home_description": "Vel kva for innhald du vil ha på Firefox-startsida di.",
   "prefs_content_discovery_header": "Firefox startside",
   "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_button": "Slå av innhaldsoppdaging",
   "prefs_section_rows_option": "{num} rekkje;{num} rekkjer",
   "prefs_search_header": "Nettsøk",
   "prefs_topsites_description": "Sidene du besøkjer mest",
   "prefs_topstories_description2": "Bra innhald frå heile nettet, tilpassa for deg",
   "prefs_topstories_options_sponsored_label": "Sponsa historiar",
   "prefs_topstories_sponsored_learn_more": "Les meir",
   "prefs_highlights_description": "Eit utval av nettsider som du har lagra eller besøkt",
   "prefs_highlights_options_visited_label": "Besøkte sider",
--- a/browser/components/newtab/prerendered/locales/sk/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/sk/activity-stream-strings.js
@@ -35,19 +35,19 @@ window.gActivityStreamStrings = {
   "search_button": "Hľadať",
   "search_header": "Vyhľadávanie pomocou {search_engine_name}",
   "search_web_placeholder": "Vyhľadávanie na webe",
   "section_disclaimer_topstories": "Najzaujímavejšie príbehy na webe, vybrané na základe toho, čo čítate. Od Pocketu, súčasti Mozilly.",
   "section_disclaimer_topstories_linktext": "Pozrite sa, ako to funguje.",
   "section_disclaimer_topstories_buttontext": "Ok, rozumiem",
   "prefs_home_header": "Obsah domovskej stránky Firefoxu",
   "prefs_home_description": "Vyberte si obsah, ktorý chcete mať na domovskej stránke svojho Firefoxu.",
-  "prefs_content_discovery_header": "Firefox Home",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_header": "Domovská stránka Firefoxu",
+  "prefs_content_discovery_description": "Odporúčanie obsahu na domovskej stránke Firefoxu vám umožňuje objaviť vysokokvalitné a relevantné články z celého internetu.",
+  "prefs_content_discovery_button": "Vypnúť odporúčanie obsahu",
   "prefs_section_rows_option": "{num} riadok;{num} riadky;{num} riadkov",
   "prefs_search_header": "Vyhľadávanie na webe",
   "prefs_topsites_description": "Najnavštevovanejšie stránky",
   "prefs_topstories_description2": "Skvelý obsah z celého webu, vybraný špeciálne pre vás",
   "prefs_topstories_options_sponsored_label": "Sponzorované stránky",
   "prefs_topstories_sponsored_learn_more": "Ďalšie informácie",
   "prefs_highlights_description": "Výber stránok, ktoré ste si uložili alebo ste ich navštívili",
   "prefs_highlights_options_visited_label": "Navštívené stránky",
@@ -105,11 +105,10 @@ window.gActivityStreamStrings = {
   "firstrun_form_sub_header": "a používajte službu Firefox Sync.",
   "firstrun_email_input_placeholder": "E-mail",
   "firstrun_invalid_input": "Vyžaduje sa platná e-mailová adresa",
   "firstrun_extra_legal_links": "Pokračovaním súhlasíte s {terms} a {privacy}.",
   "firstrun_terms_of_service": "podmienkami používania služby",
   "firstrun_privacy_notice": "zásadami ochrany súkromia",
   "firstrun_continue_to_login": "Pokračovať",
   "firstrun_skip_login": "Preskočiť tento krok",
-  "context_menu_title": "Otvorí ponuku",
-  "pocket_learn_more": "Ďalšie informácie"
+  "context_menu_title": "Otvorí ponuku"
 };
--- a/browser/components/newtab/prerendered/locales/th/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/th/activity-stream-strings.js
@@ -36,18 +36,18 @@ window.gActivityStreamStrings = {
   "search_header": "ค้นหา {search_engine_name}",
   "search_web_placeholder": "ค้นหาเว็บ",
   "section_disclaimer_topstories": "เรื่องราวที่น่าสนใจที่สุดบนเว็บ เลือกตามสิ่งที่คุณอ่าน จาก Pocket ซึ่งขณะนี้เป็นส่วนหนึ่งของ Mozilla",
   "section_disclaimer_topstories_linktext": "เรียนรู้วิธีการทำงาน",
   "section_disclaimer_topstories_buttontext": "ตกลง เข้าใจแล้ว",
   "prefs_home_header": "เนื้อหาหน้าแรก Firefox",
   "prefs_home_description": "เลือกเนื้อหาที่คุณต้องการในหน้าจอหน้าแรก Firefox ของคุณ",
   "prefs_content_discovery_header": "Firefox Home",
-  "prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
-  "prefs_content_discovery_button": "Turn Off Content Discovery",
+  "prefs_content_discovery_description": "Content Discovery ใน Firefox Home ช่วยให้คุณค้นพบบทความที่มีคุณภาพและมีความเกี่ยวข้องสูงจากทั่วทั้งเว็บ",
+  "prefs_content_discovery_button": "ปิดการค้นพบเนื้อหา",
   "prefs_section_rows_option": "{num} แถว",
   "prefs_search_header": "การค้นหาเว็บ",
   "prefs_topsites_description": "ไซต์ที่คุณเยี่ยมชมมากที่สุด",
   "prefs_topstories_description2": "เนื้อหาที่ยอดเยี่ยมจากเว็บต่าง ๆ ปรับแต่งให้เป็นส่วนบุคคลเพื่อคุณ",
   "prefs_topstories_options_sponsored_label": "เรื่องราวที่ได้รับการสนับสนุน",
   "prefs_topstories_sponsored_learn_more": "เรียนรู้เพิ่มเติม",
   "prefs_highlights_description": "การคัดเลือกไซต์ที่คุณได้บันทึกไว้หรือเยี่ยมชม",
   "prefs_highlights_options_visited_label": "หน้าที่เยี่ยมชมแล้ว",
@@ -73,19 +73,19 @@ window.gActivityStreamStrings = {
   "topsites_form_add_button": "เพิ่ม",
   "topsites_form_save_button": "บันทึก",
   "topsites_form_cancel_button": "ยกเลิก",
   "topsites_form_url_validation": "ต้องการ URL ที่ถูกต้อง",
   "topsites_form_image_validation": "ไม่สามารถโหลดภาพ ลอง URL อื่น",
   "pocket_read_more": "หัวข้อยอดนิยม:",
   "pocket_read_even_more": "ดูเรื่องราวเพิ่มเติม",
   "pocket_more_reccommendations": "คำแนะนำเพิ่มเติม",
-  "pocket_how_it_works": "How it works",
+  "pocket_how_it_works": "วิธีการทำงาน",
   "pocket_cta_button": "รับ Pocket",
-  "pocket_cta_text": "Save the stories you love in Pocket, and fuel your mind with fascinating reads.",
+  "pocket_cta_text": "บันทึกเรื่องราวที่คุณรักลงใน Pocket และเติมเต็มสมองของคุณด้วยบทความที่น่าหลงใหล",
   "highlights_empty_state": "เริ่มการท่องเว็บและเราจะแสดงบทความ, วิดีโอ และหน้าอื่น ๆ บางส่วนที่ยอดเยี่ยมที่คุณได้เยี่ยมชมหรือเพิ่มที่คั่นหน้าไว้ล่าสุดที่นี่",
   "topstories_empty_state": "คุณได้อ่านเรื่องราวครบทั้งหมดแล้ว คุณสามารถกลับมาตรวจดูเรื่องราวเด่นจาก {provider} ได้ภายหลัง อดใจรอไม่ได้งั้นหรือ? เลือกหัวข้อยอดนิยมเพื่อค้นหาเรื่องราวที่ยอดเยี่ยมจากเว็บต่าง ๆ",
   "manual_migration_explanation2": "ลอง Firefox ด้วยที่คั่นหน้า, ประวัติ และรหัสผ่านจากเบราว์เซอร์อื่น",
   "manual_migration_cancel_button": "ไม่ ขอบคุณ",
   "manual_migration_import_button": "นำเข้าตอนนี้",
   "error_fallback_default_info": "อุปส์ มีบางอย่างผิดพลาดในการโหลดเนื้อหานี้",
   "error_fallback_default_refresh_suggestion": "เรียกหน้าใหม่เพื่อลองอีกครั้ง",
   "section_menu_action_remove_section": "เอาส่วนออก",
@@ -105,11 +105,10 @@ window.gActivityStreamStrings = {
   "firstrun_form_sub_header": "เพื่อดำเนินการต่อไปยัง Firefox Sync",
   "firstrun_email_input_placeholder": "อีเมล",
   "firstrun_invalid_input": "ต้องการอีเมลที่ถูกต้อง",
   "firstrun_extra_legal_links": "เพื่อดำเนินการต่อ คุณยอมรับ {terms} และ {privacy}",
   "firstrun_terms_of_service": "เงื่อนไขการให้บริการ",
   "firstrun_privacy_notice": "ประกาศความเป็นส่วนตัว",
   "firstrun_continue_to_login": "ดำเนินการต่อ",
   "firstrun_skip_login": "ข้ามขั้นตอนนี้",
-  "context_menu_title": "เปิดเมนู",
-  "pocket_learn_more": "เรียนรู้เพิ่มเติม"
+  "context_menu_title": "เปิดเมนู"
 };
--- a/browser/components/newtab/prerendered/locales/uk/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/uk/activity-stream-strings.js
@@ -39,17 +39,17 @@ window.gActivityStreamStrings = {
   "section_disclaimer_topstories_linktext": "Дізнайтеся, як це працює.",
   "section_disclaimer_topstories_buttontext": "Гаразд, зрозуміло",
   "prefs_home_header": "Домівка Firefox",
   "prefs_home_description": "Оберіть бажаний вміст для показу в домівці Firefox.",
   "prefs_content_discovery_header": "Домівка Firefox",
   "prefs_content_discovery_description": "Огляд вмісту в домівці Firefox дозволяє вам знаходити високоякісні, цікаві статті з усього інтернету.",
   "prefs_content_discovery_button": "Вимкнути огляд вмісту",
   "prefs_section_rows_option": "{num} рядок;{num} рядки;{num} рядків",
-  "prefs_search_header": "Веб пошук",
+  "prefs_search_header": "Пошук в Інтернеті",
   "prefs_topsites_description": "Сайти, які ви відвідуєте найчастіше",
   "prefs_topstories_description2": "Чудові матеріали з усього інтернету, відібрані спеціально для вас",
   "prefs_topstories_options_sponsored_label": "Матеріали від спонсорів",
   "prefs_topstories_sponsored_learn_more": "Докладніше",
   "prefs_highlights_description": "Відібрані веб-сайти, які ви зберегли чи відвідали",
   "prefs_highlights_options_visited_label": "Відвідані сторінки",
   "prefs_highlights_options_download_label": "Останні завантаження",
   "prefs_highlights_options_pocket_label": "Збережене в Pocket",
--- a/browser/components/newtab/test/browser/browser_asrouter_targeting.js
+++ b/browser/components/newtab/test/browser/browser_asrouter_targeting.js
@@ -418,8 +418,19 @@ add_task(async function check_xpinstall_
   is(await ASRouterTargeting.Environment.xpinstallEnabled, true);
   // flip to false, check targeting reflects that
   await pushPrefs(["xpinstall.enabled", false]);
   is(await ASRouterTargeting.Environment.xpinstallEnabled, false);
   // flip to true, check targeting reflects that
   await pushPrefs(["xpinstall.enabled", true]);
   is(await ASRouterTargeting.Environment.xpinstallEnabled, true);
 });
+
+add_task(async function check_pinned_tabs() {
+  await BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, async browser => {
+    is(await ASRouterTargeting.Environment.hasPinnedTabs, false, "No pin tabs yet");
+
+    let tab = gBrowser.getTabForBrowser(browser);
+    gBrowser.pinTab(tab);
+
+    is(await ASRouterTargeting.Environment.hasPinnedTabs, true, "Should detect pinned tab");
+  });
+});
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -921,16 +921,28 @@ describe("ASRouter", () => {
 
         assert.calledOnce(global.Services.obs.removeObserver);
         assert.calledOnce(channel.sendAsyncMessage);
         assert.calledOnce(Router.blockMessageById);
         assert.calledWithExactly(Router.blockMessageById, "RETURN_TO_AMO_1");
       });
     });
 
+    describe("#onMessage: PIN_CURRENT_TAB", () => {
+      it("should call pin tab with the selectedTab", async () => {
+        const msg = fakeExecuteUserAction({type: "PIN_CURRENT_TAB"});
+        const {gBrowser} = msg.target.browser.ownerGlobal;
+
+        await Router.onMessage(msg);
+
+        assert.calledOnce(gBrowser.pinTab);
+        assert.calledWithExactly(gBrowser.pinTab, gBrowser.selectedTab);
+      });
+    });
+
     describe("#dispatch(action, target)", () => {
       it("should an action and target to onMessage", async () => {
         // use the IMPRESSION action to make sure actions are actually getting processed
         sandbox.stub(Router, "addImpression");
         sandbox.spy(Router, "onMessage");
         const target = {};
         const action = {type: "IMPRESSION"};
 
--- a/browser/components/newtab/test/unit/asrouter/ASRouterTargeting.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouterTargeting.test.js
@@ -65,9 +65,28 @@ describe("#CachedTargetingGetter", () =>
     const messages = (await OnboardingMessageProvider.getUntranslatedMessages())
       .filter(m => m.id !== "RETURN_TO_AMO_1");
     const context = {attributionData: {campaign: "non-fx-button", source: "addons.mozilla.org"}};
     const result = await ASRouterTargeting.findMatchingMessage({messages, trigger: {id: "firstRun"}, context});
 
     assert.isDefined(result);
     assert.equal(result.id, "FXA_1");
   });
+  describe("combineContexts", () => {
+    it("should combine the properties of the two objects", () => {
+      const joined = ASRouterTargeting.combineContexts({
+        get foo() { return "foo"; },
+      }, {
+        get bar() { return "bar"; },
+      });
+      assert.propertyVal(joined, "foo", "foo");
+      assert.propertyVal(joined, "bar", "bar");
+    });
+    it("should warn when properties overlap", () => {
+      ASRouterTargeting.combineContexts({
+        get foo() { return "foo"; },
+      }, {
+        get foo() { return "bar"; },
+      });
+      assert.calledOnce(global.Cu.reportError);
+    });
+  });
 });
--- a/browser/components/newtab/test/unit/asrouter/ASRouterTriggerListeners.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouterTriggerListeners.test.js
@@ -3,38 +3,93 @@ import {ASRouterTriggerListeners} from "
 describe("ASRouterTriggerListeners", () => {
   let sandbox;
   let registerWindowNotificationStub;
   let unregisterWindoNotificationStub;
   let windowEnumeratorStub;
   let existingWindow;
   const triggerHandler = () => {};
   const openURLListener = ASRouterTriggerListeners.get("openURL");
+  const frequentVisitsListener = ASRouterTriggerListeners.get("frequentVisits");
   const hosts = ["www.mozilla.com", "www.mozilla.org"];
 
   function resetEnumeratorStub(windows) {
     windowEnumeratorStub
       .withArgs("navigator:browser")
       .returns(windows);
   }
 
   beforeEach(async () => {
     sandbox = sinon.createSandbox();
     registerWindowNotificationStub = sandbox.stub(global.Services.ww, "registerNotification");
     unregisterWindoNotificationStub = sandbox.stub(global.Services.ww, "unregisterNotification");
-    existingWindow = {gBrowser: {addTabsProgressListener: sandbox.stub(), removeTabsProgressListener: sandbox.stub()}, gBrowserInit: {delayedStartupFinished: true}};
+    existingWindow = {gBrowser: {addTabsProgressListener: sandbox.stub(), removeTabsProgressListener: sandbox.stub(), currentURI: {host: ""}}, gBrowserInit: {delayedStartupFinished: true}, addEventListener: sinon.stub(), removeEventListener: sinon.stub()};
     windowEnumeratorStub = sandbox.stub(global.Services.wm, "getEnumerator");
     resetEnumeratorStub([existingWindow]);
     sandbox.spy(openURLListener, "init");
     sandbox.spy(openURLListener, "uninit");
   });
   afterEach(() => {
     sandbox.restore();
   });
 
+  describe("frequentVisits", () => {
+    let _triggerHandler;
+    beforeEach(async () => {
+      _triggerHandler = sandbox.stub();
+      sandbox.useFakeTimers();
+      await frequentVisitsListener.init(_triggerHandler, hosts);
+    });
+    afterEach(() => {
+      sandbox.clock.restore();
+      frequentVisitsListener.uninit();
+    });
+    it("should be initialized", () => {
+      assert.isTrue(frequentVisitsListener._initialized);
+    });
+    it("should listen for TabSelect events", () => {
+      assert.calledOnce(existingWindow.addEventListener);
+      assert.calledWith(existingWindow.addEventListener, "TabSelect", frequentVisitsListener.onTabSwitch);
+    });
+    it("should call _triggerHandler if the visit is valid (is recoreded)", () => {
+      frequentVisitsListener.triggerHandler({}, "www.mozilla.com");
+
+      assert.calledOnce(_triggerHandler);
+    });
+    it("should call _triggerHandler only once", () => {
+      frequentVisitsListener.triggerHandler({}, "www.mozilla.com");
+      frequentVisitsListener.triggerHandler({}, "www.mozilla.com");
+
+      assert.calledOnce(_triggerHandler);
+    });
+    it("should call _triggerHandler again after 15 minutes", () => {
+      frequentVisitsListener.triggerHandler({}, "www.mozilla.com");
+      sandbox.clock.tick(15 * 60 * 1000 + 1);
+      frequentVisitsListener.triggerHandler({}, "www.mozilla.com");
+
+      assert.calledTwice(_triggerHandler);
+    });
+    it("should call triggerHandler on valid hosts", () => {
+      const stub = sandbox.stub(frequentVisitsListener, "triggerHandler");
+      existingWindow.gBrowser.currentURI.host = hosts[0]; // eslint-disable-line prefer-destructuring
+
+      frequentVisitsListener.onTabSwitch({target: {ownerGlobal: existingWindow}});
+
+      assert.calledOnce(stub);
+    });
+    it("should not call triggerHandler on invalid hosts", () => {
+      const stub = sandbox.stub(frequentVisitsListener, "triggerHandler");
+      existingWindow.gBrowser.currentURI.host = "foo.com";
+
+      frequentVisitsListener.onTabSwitch({target: {ownerGlobal: existingWindow}});
+
+      assert.notCalled(stub);
+    });
+  });
+
   describe("openURL listener", () => {
     it("should exist and initially be uninitialised", () => {
       assert.ok(openURLListener);
       assert.notOk(openURLListener._initialized);
     });
 
     describe("#init", () => {
       beforeEach(async () => {
--- a/browser/components/newtab/test/unit/asrouter/CFRMessageProvider.test.js
+++ b/browser/components/newtab/test/unit/asrouter/CFRMessageProvider.test.js
@@ -6,28 +6,30 @@ const REGULAR_IDS = [
   "GOOGLE_TRANSLATE",
   "YOUTUBE_ENHANCE",
   // These are excluded for now.
   // "WIKIPEDIA_CONTEXT_MENU_SEARCH",
   // "REDDIT_ENHANCEMENT",
 ];
 
 describe("CFRMessageProvider", () => {
-  it("should have a total of 3 messages", () => {
+  it("should have a total of 4 messages", () => {
     assert.lengthOf(messages, 3);
   });
   it("should have one message each for the three regular addons", () => {
     for (const id of REGULAR_IDS) {
       const cohort3 = messages.find(msg => msg.id === `${id}_3`);
       assert.ok(cohort3, `contains three day cohort for ${id}`);
       assert.deepEqual(cohort3.frequency, {lifetime: 3}, "three day cohort has the right frequency cap");
       assert.notInclude(cohort3.targeting, `providerCohorts.cfr`);
     }
   });
   it("should always have xpinstallEnabled as targeting if it is an addon", () => {
     for (const message of messages) {
       // Ensure that the CFR messages that are recommending an addon have this targeting.
       // In the future when we can do targeting based on category, this test will change.
       // See bug 1494778 and 1497653
-      assert.include(message.targeting, `(xpinstallEnabled == true)`);
+      if (message.id !== "PIN_TAB") {
+        assert.include(message.targeting, `(xpinstallEnabled == true)`);
+      }
     }
   });
 });
--- a/browser/components/newtab/test/unit/asrouter/CFRPageActions.test.js
+++ b/browser/components/newtab/test/unit/asrouter/CFRPageActions.test.js
@@ -119,80 +119,80 @@ describe("CFRPageActions", () => {
     let pageAction;
     let getStringsStub;
 
     beforeEach(() => {
       pageAction = new PageAction(window, dispatchStub);
       getStringsStub = sandbox.stub(pageAction, "getStrings").resolves("");
     });
 
-    describe("#show", () => {
-      it("should un-hide the element and set the right label value", async () => {
+    describe("#showAddressBarNotifier", () => {
+      it("should un-hideAddressBarNotifier the element and set the right label value", async () => {
         const FAKE_NOTIFICATION_TEXT = "FAKE_NOTIFICATION_TEXT";
         getStringsStub.withArgs(fakeRecommendation.content.notification_text).resolves(FAKE_NOTIFICATION_TEXT);
-        await pageAction.show(fakeRecommendation);
+        await pageAction.showAddressBarNotifier(fakeRecommendation);
         assert.isFalse(pageAction.container.hidden);
         assert.equal(pageAction.label.value, FAKE_NOTIFICATION_TEXT);
       });
       it("should wait for the document layout to flush", async () => {
         sandbox.spy(pageAction.label, "getClientRects");
-        await pageAction.show(fakeRecommendation);
+        await pageAction.showAddressBarNotifier(fakeRecommendation);
         assert.calledOnce(global.promiseDocumentFlushed);
         assert.callOrder(global.promiseDocumentFlushed, pageAction.label.getClientRects);
       });
       it("should set the CSS variable --cfr-label-width correctly", async () => {
-        await pageAction.show(fakeRecommendation);
+        await pageAction.showAddressBarNotifier(fakeRecommendation);
         const expectedWidth = pageAction.label.getClientRects()[0].width;
         assert.equal(pageAction.urlbar.style.getPropertyValue("--cfr-label-width"),
           `${expectedWidth}px`);
       });
       it("should cause an expansion, and dispatch an impression iff `expand` is true", async () => {
         sandbox.spy(pageAction, "_clearScheduledStateChanges");
         sandbox.spy(pageAction, "_expand");
         sandbox.spy(pageAction, "_dispatchImpression");
 
-        await pageAction.show(fakeRecommendation);
+        await pageAction.showAddressBarNotifier(fakeRecommendation);
         assert.notCalled(pageAction._dispatchImpression);
         clock.tick(1001);
         assert.notEqual(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "expanded");
 
-        await pageAction.show(fakeRecommendation, true);
+        await pageAction.showAddressBarNotifier(fakeRecommendation, true);
         assert.calledOnce(pageAction._clearScheduledStateChanges);
         clock.tick(1001);
         assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "expanded");
         assert.calledOnce(pageAction._dispatchImpression);
         assert.calledWith(pageAction._dispatchImpression, fakeRecommendation);
       });
       it("should send telemetry if `expand` is true and the id and bucket_id are provided", async () => {
-        await pageAction.show(fakeRecommendation, true);
+        await pageAction.showAddressBarNotifier(fakeRecommendation, true);
         assert.calledWith(dispatchStub, {
           type: "DOORHANGER_TELEMETRY",
           data: {
             action: "cfr_user_event",
             source: "CFR",
             message_id: fakeRecommendation.id,
             bucket_id: fakeRecommendation.content.bucket_id,
             event: "IMPRESSION",
           },
         });
       });
     });
 
-    describe("#hide", () => {
-      it("should hide the container, cancel any state changes, and remove the state attribute", () => {
+    describe("#hideAddressBarNotifier", () => {
+      it("should hideAddressBarNotifier the container, cancel any state changes, and remove the state attribute", () => {
         sandbox.spy(pageAction, "_clearScheduledStateChanges");
-        pageAction.hide();
+        pageAction.hideAddressBarNotifier();
         assert.isTrue(pageAction.container.hidden);
         assert.calledOnce(pageAction._clearScheduledStateChanges);
         assert.isNull(pageAction.urlbar.getAttribute("cfr-recommendation-state"));
       });
       it("should remove the `currentNotification`", () => {
         const notification = {};
         pageAction.currentNotification = notification;
-        pageAction.hide();
+        pageAction.hideAddressBarNotifier();
         assert.calledWith(global.PopupNotifications.remove, notification);
       });
     });
 
     describe("#_expand", () => {
       beforeEach(() => {
         pageAction._clearScheduledStateChanges();
         pageAction.urlbar.removeAttribute("cfr-recommendation-state");
@@ -392,17 +392,17 @@ describe("CFRPageActions", () => {
 
         await pageAction.getStrings(fromJson, "accesskey");
 
         assert.calledOnce(stub);
         stub.restore();
       });
     });
 
-    describe("#_handleClick", () => {
+    describe("#_showPopupOnClick", () => {
       beforeEach(async () => {
         CFRPageActions.PageActionMap.set(fakeBrowser.ownerGlobal, pageAction);
         await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
         getStringsStub.callsFake(async a => a) // eslint-disable-line max-nested-callbacks
           .withArgs({string_id: "primary_button_id"})
           .resolves({value: "Primary Button", attributes: {accesskey: "p"}})
           .withArgs({string_id: "secondary_button_id"})
           .resolves({value: "Secondary Button", attributes: {accesskey: "s"}})
@@ -411,78 +411,78 @@ describe("CFRPageActions", () => {
           .withArgs({string_id: "secondary_button_id_3"})
           .resolves({value: "Secondary Button 3", attributes: {accesskey: "g"}})
           .withArgs(sinon.match({string_id: "cfr-doorhanger-extension-learn-more-link"}))
           .resolves("Learn more")
           .withArgs(sinon.match({string_id: "cfr-doorhanger-extension-total-users"}))
           .callsFake(async ({args}) => `${args.total} users`); // eslint-disable-line max-nested-callbacks
       });
 
-      it("should call `.hide` and do nothing if there is no recommendation for the selected browser", async () => {
-        sandbox.spy(pageAction, "hide");
+      it("should call `.hideAddressBarNotifier` and do nothing if there is no recommendation for the selected browser", async () => {
+        sandbox.spy(pageAction, "hideAddressBarNotifier");
         CFRPageActions.RecommendationMap.delete(fakeBrowser);
-        await pageAction._handleClick({});
-        assert.calledOnce(pageAction.hide);
+        await pageAction._showPopupOnClick({});
+        assert.calledOnce(pageAction.hideAddressBarNotifier);
         assert.notCalled(global.PopupNotifications.show);
       });
       it("should cancel any planned state changes", async () => {
         sandbox.spy(pageAction, "_clearScheduledStateChanges");
         assert.notCalled(pageAction._clearScheduledStateChanges);
-        await pageAction._handleClick({});
+        await pageAction._showPopupOnClick({});
         assert.calledOnce(pageAction._clearScheduledStateChanges);
       });
       it("should set the right text values", async () => {
-        await pageAction._handleClick({});
+        await pageAction._showPopupOnClick({});
         const headerLabel = elements["cfr-notification-header-label"];
         const headerLink = elements["cfr-notification-header-link"];
         const headerImage = elements["cfr-notification-header-image"];
         const footerText = elements["cfr-notification-footer-text"];
         const footerLink = elements["cfr-notification-footer-learn-more-link"];
         assert.equal(headerLabel.value, fakeRecommendation.content.heading_text);
         assert.isTrue(headerLink.getAttribute("href").endsWith(fakeRecommendation.content.info_icon.sumo_path));
         assert.equal(headerImage.getAttribute("tooltiptext"), fakeRecommendation.content.info_icon.label);
         assert.equal(footerText.textContent, fakeRecommendation.content.text);
         assert.equal(footerLink.value, "Learn more");
         assert.equal(footerLink.getAttribute("href"), fakeRecommendation.content.addon.amo_url);
       });
       it("should add the rating correctly", async () => {
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         const footerFilledStars = elements["cfr-notification-footer-filled-stars"];
         const footerEmptyStars = elements["cfr-notification-footer-empty-stars"];
         // .toFixed to sort out some floating precision errors
         assert.equal(footerFilledStars.style.width, `${(4.2 * 17).toFixed(1)}px`);
         assert.equal(footerEmptyStars.style.width, `${(0.8 * 17).toFixed(1)}px`);
       });
       it("should add the number of users correctly", async () => {
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         const footerUsers = elements["cfr-notification-footer-users"];
         assert.isNull(footerUsers.getAttribute("hidden"));
         assert.equal(footerUsers.getAttribute("value"), `${fakeRecommendation.content.addon.users} users`);
       });
       it("should send the right telemetry", async () => {
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         assert.calledWith(dispatchStub, {
           type: "DOORHANGER_TELEMETRY",
           data: {
             action: "cfr_user_event",
             source: "CFR",
             message_id: fakeRecommendation.id,
             bucket_id: fakeRecommendation.content.bucket_id,
             event: "CLICK_DOORHANGER",
           },
         });
       });
       it("should set the main action correctly", async () => {
         sinon.stub(CFRPageActions, "_fetchLatestAddonVersion").resolves("latest-addon.xpi");
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         const mainAction = global.PopupNotifications.show.firstCall.args[4]; // eslint-disable-line prefer-destructuring
         assert.deepEqual(mainAction.label, {value: "Primary Button", attributes: {accesskey: "p"}});
-        sandbox.spy(pageAction, "hide");
+        sandbox.spy(pageAction, "hideAddressBarNotifier");
         await mainAction.callback();
-        assert.calledOnce(pageAction.hide);
+        assert.calledOnce(pageAction.hideAddressBarNotifier);
         // Should block the message
         assert.calledWith(dispatchStub, {
           type: "BLOCK_MESSAGE_BY_ID",
           data: {id: fakeRecommendation.id},
         });
         // Should trigger the action
         assert.calledWith(
           dispatchStub,
@@ -499,88 +499,88 @@ describe("CFRPageActions", () => {
             bucket_id: fakeRecommendation.content.bucket_id,
             event: "INSTALL",
           },
         });
         // Should remove the recommendation
         assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
       });
       it("should set the secondary action correctly", async () => {
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         const [secondaryAction] = global.PopupNotifications.show.firstCall.args[5]; // eslint-disable-line prefer-destructuring
 
         assert.deepEqual(secondaryAction.label, {value: "Secondary Button", attributes: {accesskey: "s"}});
-        sandbox.spy(pageAction, "hide");
+        sandbox.spy(pageAction, "hideAddressBarNotifier");
         CFRPageActions.RecommendationMap.set(fakeBrowser, {});
         secondaryAction.callback();
         // Should send telemetry
         assert.calledWith(dispatchStub, {
           type: "DOORHANGER_TELEMETRY",
           data: {
             action: "cfr_user_event",
             source: "CFR",
             message_id: fakeRecommendation.id,
             bucket_id: fakeRecommendation.content.bucket_id,
             event: "DISMISS",
           },
         });
         // Don't remove the recommendation on `DISMISS` action
         assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
-        assert.notCalled(pageAction.hide);
+        assert.notCalled(pageAction.hideAddressBarNotifier);
       });
       it("should send right telemetry for BLOCK secondary action", async () => {
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         const blockAction = global.PopupNotifications.show.firstCall.args[5][1]; // eslint-disable-line prefer-destructuring
 
         assert.deepEqual(blockAction.label, {value: "Secondary Button 2", attributes: {accesskey: "a"}});
-        sandbox.spy(pageAction, "hide");
+        sandbox.spy(pageAction, "hideAddressBarNotifier");
         sandbox.spy(pageAction, "_blockMessage");
         CFRPageActions.RecommendationMap.set(fakeBrowser, {});
         blockAction.callback();
-        assert.calledOnce(pageAction.hide);
+        assert.calledOnce(pageAction.hideAddressBarNotifier);
         assert.calledOnce(pageAction._blockMessage);
         // Should send telemetry
         assert.calledWith(dispatchStub, {
           type: "DOORHANGER_TELEMETRY",
           data: {
             action: "cfr_user_event",
             source: "CFR",
             message_id: fakeRecommendation.id,
             bucket_id: fakeRecommendation.content.bucket_id,
             event: "BLOCK",
           },
         });
         // Should remove the recommendation
         assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
       });
       it("should send right telemetry for MANAGE secondary action", async () => {
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         const manageAction = global.PopupNotifications.show.firstCall.args[5][2]; // eslint-disable-line prefer-destructuring
 
         assert.deepEqual(manageAction.label, {value: "Secondary Button 3", attributes: {accesskey: "g"}});
-        sandbox.spy(pageAction, "hide");
+        sandbox.spy(pageAction, "hideAddressBarNotifier");
         CFRPageActions.RecommendationMap.set(fakeBrowser, {});
         manageAction.callback();
         // Should send telemetry
         assert.calledWith(dispatchStub, {
           type: "DOORHANGER_TELEMETRY",
           data: {
             action: "cfr_user_event",
             source: "CFR",
             message_id: fakeRecommendation.id,
             bucket_id: fakeRecommendation.content.bucket_id,
             event: "MANAGE",
           },
         });
         // Don't remove the recommendation on `MANAGE` action
         assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
-        assert.notCalled(pageAction.hide);
+        assert.notCalled(pageAction.hideAddressBarNotifier);
       });
       it("should call PopupNotifications.show with the right arguments", async () => {
-        await pageAction._handleClick();
+        await pageAction._showPopupOnClick();
         assert.calledWith(
           global.PopupNotifications.show,
           fakeBrowser,
           "contextual-feature-recommendation",
           fakeRecommendation.content.addon.title,
           "cfr",
           sinon.match.any, // Corresponds to the main action, tested above
           sinon.match.any, // Corresponds to the secondary action, tested above
@@ -592,18 +592,18 @@ describe("CFRPageActions", () => {
         );
       });
     });
   });
 
   describe("CFRPageActions", () => {
     beforeEach(() => {
       // Spy on the prototype methods to inspect calls for any PageAction instance
-      sandbox.spy(PageAction.prototype, "show");
-      sandbox.spy(PageAction.prototype, "hide");
+      sandbox.spy(PageAction.prototype, "showAddressBarNotifier");
+      sandbox.spy(PageAction.prototype, "hideAddressBarNotifier");
     });
 
     describe("updatePageActions", () => {
       let savedRec;
 
       beforeEach(() => {
         const win = fakeBrowser.ownerGlobal;
         CFRPageActions.PageActionMap.set(win, new PageAction(win, dispatchStub));
@@ -615,61 +615,61 @@ describe("CFRPageActions", () => {
         };
         CFRPageActions.RecommendationMap.set(fakeBrowser, savedRec);
       });
 
       it("should do nothing if a pageAction doesn't exist for the window", () => {
         const win = fakeBrowser.ownerGlobal;
         CFRPageActions.PageActionMap.delete(win);
         CFRPageActions.updatePageActions(fakeBrowser);
-        assert.notCalled(PageAction.prototype.show);
-        assert.notCalled(PageAction.prototype.hide);
+        assert.notCalled(PageAction.prototype.showAddressBarNotifier);
+        assert.notCalled(PageAction.prototype.hideAddressBarNotifier);
       });
       it("should do nothing if the browser is not the `selectedBrowser`", () => {
         const someOtherFakeBrowser = {};
         CFRPageActions.updatePageActions(someOtherFakeBrowser);
-        assert.notCalled(PageAction.prototype.show);
-        assert.notCalled(PageAction.prototype.hide);
+        assert.notCalled(PageAction.prototype.showAddressBarNotifier);
+        assert.notCalled(PageAction.prototype.hideAddressBarNotifier);
       });
-      it("should hide the pageAction if a recommendation doesn't exist for the given browser", () => {
+      it("should hideAddressBarNotifier the pageAction if a recommendation doesn't exist for the given browser", () => {
         CFRPageActions.RecommendationMap.delete(fakeBrowser);
         CFRPageActions.updatePageActions(fakeBrowser);
-        assert.calledOnce(PageAction.prototype.hide);
+        assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
       });
       it("should show the pageAction if a recommendation exists and the host matches", () => {
         CFRPageActions.updatePageActions(fakeBrowser);
-        assert.calledOnce(PageAction.prototype.show);
-        assert.calledWith(PageAction.prototype.show, savedRec);
+        assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
+        assert.calledWith(PageAction.prototype.showAddressBarNotifier, savedRec);
       });
-      it("should hide the pageAction and delete the recommendation if the recommendation exists but the host doesn't match", () => {
+      it("should hideAddressBarNotifier the pageAction and delete the recommendation if the recommendation exists but the host doesn't match", () => {
         const someOtherFakeHost = "subdomain.mozilla.com";
         fakeBrowser.documentURI.host = someOtherFakeHost;
         assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
         CFRPageActions.updatePageActions(fakeBrowser);
-        assert.calledOnce(PageAction.prototype.hide);
+        assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
         assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
       });
       it("should not call `delete` if retain is true", () => {
         savedRec.retain = true;
         fakeBrowser.documentURI.host = "subdomain.mozilla.com";
         assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
 
         CFRPageActions.updatePageActions(fakeBrowser);
         assert.propertyVal(savedRec, "retain", false);
-        assert.calledOnce(PageAction.prototype.hide);
+        assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
         assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
       });
       it("should call `delete` if retain is false", () => {
         savedRec.retain = false;
         fakeBrowser.documentURI.host = "subdomain.mozilla.com";
         assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
 
         CFRPageActions.updatePageActions(fakeBrowser);
         assert.propertyVal(savedRec, "retain", false);
-        assert.calledOnce(PageAction.prototype.hide);
+        assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
         assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
       });
     });
 
     describe("forceRecommendation", () => {
       it("should succeed and add an element to the RecommendationMap", async () => {
         assert.isTrue(await CFRPageActions.forceRecommendation({browser: fakeBrowser}, fakeRecommendation, dispatchStub));
         assert.deepInclude(CFRPageActions.RecommendationMap.get(fakeBrowser), {
@@ -679,17 +679,17 @@ describe("CFRPageActions", () => {
       });
       it("should create a PageAction if one doesn't exist for the window, save it in the PageActionMap, and call `show`", async () => {
         const win = fakeBrowser.ownerGlobal;
         assert.isFalse(CFRPageActions.PageActionMap.has(win));
         await CFRPageActions.forceRecommendation({browser: fakeBrowser}, fakeRecommendation, dispatchStub);
         const pageAction = CFRPageActions.PageActionMap.get(win);
         assert.equal(win, pageAction.window);
         assert.equal(dispatchStub, pageAction._dispatchToASRouter);
-        assert.calledOnce(PageAction.prototype.show);
+        assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
       });
     });
 
     describe("addRecommendation", () => {
       it("should fail and not add a recommendation if the browser is part of a private window", async () => {
         global.PrivateBrowsingUtils.isWindowPrivate.returns(true);
         assert.isFalse(await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub));
         assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
@@ -712,17 +712,17 @@ describe("CFRPageActions", () => {
       });
       it("should create a PageAction if one doesn't exist for the window, save it in the PageActionMap, and call `show`", async () => {
         const win = fakeBrowser.ownerGlobal;
         assert.isFalse(CFRPageActions.PageActionMap.has(win));
         await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
         const pageAction = CFRPageActions.PageActionMap.get(win);
         assert.equal(win, pageAction.window);
         assert.equal(dispatchStub, pageAction._dispatchToASRouter);
-        assert.calledOnce(PageAction.prototype.show);
+        assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
       });
       it("should add the right url if we fetched and addon install URL", async () => {
         fakeRecommendation.template = "cfr_doorhanger";
         await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
         const recommendation = CFRPageActions.RecommendationMap.get(fakeBrowser);
 
         // sanity check - just go through some of the rest of the attributes to make sure they were untouched
         assert.equal(recommendation.id, fakeRecommendation.id);
@@ -732,37 +732,37 @@ describe("CFRPageActions", () => {
         assert.equal(recommendation.content.buttons.secondary, fakeRecommendation.content.buttons.secondary);
         assert.equal(recommendation.content.buttons.primary.action.id, fakeRecommendation.content.buttons.primary.action.id);
 
         delete fakeRecommendation.template;
       });
     });
 
     describe("clearRecommendations", () => {
-      const createFakePageAction = () => ({hide: sandbox.stub()});
+      const createFakePageAction = () => ({hideAddressBarNotifier: sandbox.stub()});
       const windows = [{}, {}, {closed: true}];
       const browsers = [{}, {}, {}, {}];
 
       beforeEach(() => {
         CFRPageActions.PageActionMap.set(windows[0], createFakePageAction());
         CFRPageActions.PageActionMap.set(windows[2], createFakePageAction());
         for (const browser of browsers) {
           CFRPageActions.RecommendationMap.set(browser, {});
         }
         globals.set({Services: {wm: {getEnumerator: () => windows}}});
       });
 
-      it("should hide the PageActions of any existing, non-closed windows", () => {
+      it("should hideAddressBarNotifier the PageActions of any existing, non-closed windows", () => {
         const pageActions = windows.map(win => CFRPageActions.PageActionMap.get(win));
         CFRPageActions.clearRecommendations();
 
         // Only the first window had a PageAction and wasn't closed
-        assert.calledOnce(pageActions[0].hide);
+        assert.calledOnce(pageActions[0].hideAddressBarNotifier);
         assert.isUndefined(pageActions[1]);
-        assert.notCalled(pageActions[2].hide);
+        assert.notCalled(pageActions[2].hideAddressBarNotifier);
       });
       it("should clear the PageActionMap and the RecommendationMap", () => {
         CFRPageActions.clearRecommendations();
 
         // Both are WeakMaps and so are not iterable, cannot be cleared, and
         // cannot have their length queried directly, so we have to check
         // whether previous elements still exist
         assert.lengthOf(windows, 3);
--- a/browser/components/newtab/test/unit/asrouter/constants.js
+++ b/browser/components/newtab/test/unit/asrouter/constants.js
@@ -27,13 +27,17 @@ export class FakeRemotePageManager {
     this.sendAsyncMessage = sinon.stub();
     this.removeMessageListener = sinon.stub();
     this.browser = {
       ownerGlobal: {
         openTrustedLinkIn: sinon.stub(),
         openLinkIn: sinon.stub(),
         OpenBrowserWindow: sinon.stub(),
         openPreferences: sinon.stub(),
+        gBrowser: {
+          pinTab: sinon.stub(),
+          selectedTab: {},
+        },
       },
     };
     this.portID = "6000:2";
   }
 }
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/ImpressionStats.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/ImpressionStats.test.jsx
@@ -22,17 +22,17 @@ describe("<ImpressionStats>", () => {
         this.callback(entries);
       }
 
       unobserve() {}
     };
   }
 
   const DEFAULT_PROPS = {
-    rows: [{id: 1}, {id: 2}, {id: 3}],
+    rows: [{id: 1, pos: 0}, {id: 2, pos: 1}, {id: 3, pos: 2}],
     source: SOURCE,
     IntersectionObserver: buildIntersectionObserver(FullIntersectEntries),
     document: {
       visibilityState: "visible",
       addEventListener: sinon.stub(),
       removeEventListener: sinon.stub(),
     },
   };
@@ -82,17 +82,18 @@ describe("<ImpressionStats>", () => {
     const props = {dispatch, IntersectionObserver: buildIntersectionObserver(FullIntersectEntries)};
     renderImpressionStats(props);
 
     assert.calledOnce(dispatch);
 
     const [action] = dispatch.firstCall.args;
     assert.equal(action.type, at.DISCOVERY_STREAM_IMPRESSION_STATS);
     assert.equal(action.data.source, SOURCE);
-    assert.deepEqual(action.data.tiles, [{id: 1}, {id: 2}, {id: 3}]);
+    assert.deepEqual(action.data.tiles,
+      [{id: 1, pos: 0}, {id: 2, pos: 1}, {id: 3, pos: 2}]);
   });
   it("should send a DISCOVERY_STREAM_SPOC_IMPRESSION when the wrapped item has a campaignId", () => {
     const dispatch = sinon.spy();
     const campaignId = "a_campaign_id";
     const props = {dispatch, campaignId, IntersectionObserver: buildIntersectionObserver(FullIntersectEntries)};
     renderImpressionStats(props);
 
     assert.calledTwice(dispatch);
@@ -106,32 +107,33 @@ describe("<ImpressionStats>", () => {
     const props = {dispatch, IntersectionObserver: buildIntersectionObserver(ZeroIntersectEntries, false)};
     const wrapper = renderImpressionStats(props);
 
     assert.notCalled(dispatch);
 
     // Simulating the full intersection change with a row change
     wrapper.setProps({
       ...props,
-      ...{rows: [{id: 1}, {id: 2}, {id: 3}]},
+      ...{rows: [{id: 1, pos: 0}, {id: 2, pos: 1}, {id: 3, pos: 2}]},
       ...{IntersectionObserver: buildIntersectionObserver(FullIntersectEntries)},
     });
 
     assert.calledOnce(dispatch);
 
     const [action] = dispatch.firstCall.args;
-    assert.deepEqual(action.data.tiles, [{id: 1}, {id: 2}, {id: 3}]);
+    assert.deepEqual(action.data.tiles,
+      [{id: 1, pos: 0}, {id: 2, pos: 1}, {id: 3, pos: 2}]);
   });
   it("should send an impression if props are updated and props.rows are different", () => {
     const props = {dispatch: sinon.spy()};
     const wrapper = renderImpressionStats(props);
     props.dispatch.resetHistory();
 
     // New rows
-    wrapper.setProps({...DEFAULT_PROPS, ...{rows: [{id: 4}]}});
+    wrapper.setProps({...DEFAULT_PROPS, ...{rows: [{id: 4, pos: 3}]}});
 
     assert.calledOnce(props.dispatch);
   });
   it("should not send an impression if props are updated but IDs are the same", () => {
     const props = {dispatch: sinon.spy()};
     const wrapper = renderImpressionStats(props);
     props.dispatch.resetHistory();
 
@@ -176,23 +178,23 @@ describe("<ImpressionStats>", () => {
         addEventListener: (ev, cb) => listeners.add(cb),
         removeEventListener: (ev, cb) => listeners.delete(cb),
       },
     };
 
     const wrapper = renderImpressionStats(props);
 
     // Update twice
-    wrapper.setProps({...props, ...{rows: [{id: 123}]}});
-    wrapper.setProps({...props, ...{rows: [{id: 2432}]}});
+    wrapper.setProps({...props, ...{rows: [{id: 123, pos: 4}]}});
+    wrapper.setProps({...props, ...{rows: [{id: 2432, pos: 5}]}});
 
     assert.notCalled(props.dispatch);
 
     // Simulate listeners getting called
     props.document.visibilityState = "visible";
     listeners.forEach(l => l());
 
     // Make sure we only sent the latest event
     assert.calledOnce(props.dispatch);
     const [action] = props.dispatch.firstCall.args;
-    assert.deepEqual(action.data.tiles, [{id: 2432}]);
+    assert.deepEqual(action.data.tiles, [{id: 2432, pos: 5}]);
   });
 });
--- a/browser/components/newtab/test/unit/content-src/lib/selectLayoutRender.test.js
+++ b/browser/components/newtab/test/unit/content-src/lib/selectLayoutRender.test.js
@@ -87,9 +87,31 @@ describe("selectLayoutRender", () => {
     globals.sandbox.stub(global.Math, "random").returns(0.6);
 
     const result = selectLayoutRender(store.getState());
 
     assert.lengthOf(result, 1);
     assert.deepEqual(result[0].components[0].data.recommendations[0], "foo");
     assert.deepEqual(result[0].components[0].data.recommendations[1], "bar");
   });
+
+  it("should return a layout with feeds of items length with positions", () => {
+    const fakeLayout = [{width: 3, components: [{type: "foo", properties: {items: 3}, feed: {url: "foo.com"}}]}];
+    const fakeRecommendations = [
+      {name: "item1"},
+      {name: "item2"},
+      {name: "item3"},
+      {name: "item4"},
+    ];
+    const fakeFeeds = {"foo.com": {data: {recommendations: fakeRecommendations}}};
+    store.dispatch({type: at.DISCOVERY_STREAM_LAYOUT_UPDATE, data: {layout: fakeLayout}});
+    store.dispatch({type: at.DISCOVERY_STREAM_FEEDS_UPDATE, data: fakeFeeds});
+
+    const result = selectLayoutRender(store.getState());
+
+    const {recommendations} = result[0].components[0].data;
+    assert.equal(recommendations.length, 4);
+    assert.equal(recommendations[0].pos, 0);
+    assert.equal(recommendations[1].pos, 1);
+    assert.equal(recommendations[2].pos, 2);
+    assert.equal(recommendations[3].pos, undefined);
+  });
 });
--- a/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
@@ -435,16 +435,110 @@ describe("DiscoveryStreamFeed", () => {
       const {secondCall} = feed.cache.set;
       const {thirdCall} = feed.cache.set;
       assert.deepEqual(firstCall.args, ["layout", {}]);
       assert.deepEqual(secondCall.args, ["feeds", {}]);
       assert.deepEqual(thirdCall.args, ["spocs", {}]);
     });
   });
 
+  describe("#transform", () => {
+    it("should return initial data if spocs are empty", () => {
+      const result = feed.transform({spocs: []});
+
+      assert.equal(result.spocs.length, 0);
+    });
+    it("should sort based on item_score", () => {
+      const result = feed.transform({
+        spocs: [
+          {campaign_id: 2, item_score: 0.8, min_score: 0.1},
+          {campaign_id: 3, item_score: 0.7, min_score: 0.1},
+          {campaign_id: 1, item_score: 0.9, min_score: 0.1},
+        ],
+      });
+
+      assert.deepEqual(result.spocs, [
+        {campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1},
+        {campaign_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1},
+        {campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1},
+      ]);
+    });
+    it("should remove items with scores lower than min_score", () => {
+      const result = feed.transform({
+        spocs: [
+          {campaign_id: 2, item_score: 0.8, min_score: 0.9},
+          {campaign_id: 3, item_score: 0.7, min_score: 0.7},
+          {campaign_id: 1, item_score: 0.9, min_score: 0.8},
+        ],
+      });
+
+      assert.deepEqual(result.spocs, [
+        {campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.8},
+        {campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.7},
+      ]);
+    });
+    it("should add a score prop to spocs", () => {
+      const result = feed.transform({
+        spocs: [
+          {campaign_id: 1, item_score: 0.9, min_score: 0.1},
+        ],
+      });
+
+      assert.equal(result.spocs[0].score, 0.9);
+    });
+    it("should filter out duplicate campigns", () => {
+      const result = feed.transform({
+        spocs: [
+          {campaign_id: 2, item_score: 0.8, min_score: 0.1},
+          {campaign_id: 3, item_score: 0.6, min_score: 0.1},
+          {campaign_id: 1, item_score: 0.9, min_score: 0.1},
+          {campaign_id: 3, item_score: 0.7, min_score: 0.1},
+          {campaign_id: 1, item_score: 0.9, min_score: 0.1},
+        ],
+      });
+
+      assert.deepEqual(result.spocs, [
+        {campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1},
+        {campaign_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1},
+        {campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1},
+      ]);
+    });
+    it("should filter out duplicate campigns while using spocs_per_domain", () => {
+      sandbox.stub(feed.store, "getState").returns({
+        DiscoveryStream: {
+          spocs: {spocs_per_domain: 2},
+        },
+      });
+
+      const result = feed.transform({
+        spocs: [
+          {campaign_id: 2, item_score: 0.8, min_score: 0.1},
+          {campaign_id: 3, item_score: 0.6, min_score: 0.1},
+          {campaign_id: 1, item_score: 0.6, min_score: 0.1},
+          {campaign_id: 3, item_score: 0.7, min_score: 0.1},
+          {campaign_id: 1, item_score: 0.9, min_score: 0.1},
+          {campaign_id: 2, item_score: 0.6, min_score: 0.1},
+          {campaign_id: 3, item_score: 0.7, min_score: 0.1},
+          {campaign_id: 1, item_score: 0.8, min_score: 0.1},
+          {campaign_id: 3, item_score: 0.7, min_score: 0.1},
+          {campaign_id: 1, item_score: 0.8, min_score: 0.1},
+        ],
+      });
+
+      assert.deepEqual(result.spocs, [
+        {campaign_id: 1, item_score: 0.9, score: 0.9, min_score: 0.1},
+        {campaign_id: 2, item_score: 0.8, score: 0.8, min_score: 0.1},
+        {campaign_id: 1, item_score: 0.8, score: 0.8, min_score: 0.1},
+        {campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1},
+        {campaign_id: 3, item_score: 0.7, score: 0.7, min_score: 0.1},
+        {campaign_id: 2, item_score: 0.6, score: 0.6, min_score: 0.1},
+      ]);
+    });
+  });
+
   describe("#filterSpocs", () => {
     it("should return filtered out spocs based on frequency caps", () => {
       const fakeSpocs = {
         spocs: [
           {
             campaign_id: "seen",
             caps: {
               lifetime: 3,
@@ -713,26 +807,30 @@ describe("DiscoveryStreamFeed", () => {
     it("should call dispatch to ac.AlsoToPreloaded with filtered spoc data", async () => {
       Object.defineProperty(feed, "showSpocs", {get: () => true});
       const fakeSpocs = {
         lastUpdated: 1,
         data: {
           spocs: [
             {
               campaign_id: "seen",
+              min_score: 0.1,
+              item_score: 1,
               caps: {
                 lifetime: 3,
                 campaign: {
                   count: 1,
                   period: 1,
                 },
               },
             },
             {
               campaign_id: "not-seen",
+              min_score: 0.1,
+              item_score: 1,
               caps: {
                 lifetime: 3,
                 campaign: {
                   count: 1,
                   period: 1,
                 },
               },
             },
@@ -741,16 +839,19 @@ describe("DiscoveryStreamFeed", () => {
       };
       const fakeImpressions = {
         "seen": [Date.now() - 1],
       };
       const result = {
         spocs: [
           {
             campaign_id: "not-seen",
+            min_score: 0.1,
+            item_score: 1,
+            score: 1,
             caps: {
               lifetime: 3,
               campaign: {
                 count: 1,
                 period: 1,
               },
             },
           },
--- a/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
@@ -22,31 +22,33 @@ describe("TelemetryFeed", () => {
   let sandbox;
   let expectedUserPrefs;
   let browser = {getAttribute() { return "true"; }};
   let instance;
   let clock;
   let fakeHomePageUrl;
   let fakeHomePage;
   let fakeExtensionSettingsStore;
-  class PingCentre {sendPing() {} uninit() {}}
+  class PingCentre {sendPing() {} uninit() {} sendStructuredIngestionPing() {}}
   class UTEventReporting {sendUserEvent() {} sendSessionEndEvent() {} uninit() {}}
   class PerfService {
     getMostRecentAbsMarkStartByName() { return 1234; }
     mark() {}
     absNow() { return 123; }
     get timeOrigin() { return 123456; }
   }
   const perfService = new PerfService();
   const {
     TelemetryFeed,
     USER_PREFS_ENCODING,
     PREF_IMPRESSION_ID,
     TELEMETRY_PREF,
     EVENTS_TELEMETRY_PREF,
+    STRUCTURED_INGESTION_TELEMETRY_PREF,
+    STRUCTURED_INGESTION_ENDPOINT_PREF,
   } = injector({
     "common/PerfService.jsm": {perfService},
     "lib/UTEventReporting.jsm": {UTEventReporting},
   });
 
   beforeEach(() => {
     globals = new GlobalOverrider();
     sandbox = globals.sandbox;
@@ -171,16 +173,31 @@ describe("TelemetryFeed", () => {
       });
 
       it("should set the enabled property to true", () => {
         instance._prefs.set(EVENTS_TELEMETRY_PREF, true);
 
         assert.propertyVal(instance, "eventTelemetryEnabled", true);
       });
     });
+    describe("Structured Ingestion telemetry pref changes from false to true", () => {
+      beforeEach(() => {
+        FakePrefs.prototype.prefs = {};
+        FakePrefs.prototype.prefs[STRUCTURED_INGESTION_TELEMETRY_PREF] = false;
+        instance = new TelemetryFeed();
+
+        assert.propertyVal(instance, "structuredIngestionTelemetryEnabled", false);
+      });
+
+      it("should set the enabled property to true", () => {
+        instance._prefs.set(STRUCTURED_INGESTION_TELEMETRY_PREF, true);
+
+        assert.propertyVal(instance, "structuredIngestionTelemetryEnabled", true);
+      });
+    });
   });
   describe("#handleEvent", () => {
     it("should dispatch a TAB_PINNED_EVENT", () => {
       sandbox.stub(instance, "sendEvent");
       globals.set({
         Services: {
           ...Services,
           wm: {getEnumerator: () => [{gBrowser: {tabs: [{pinned: true}]}}]},
@@ -782,16 +799,29 @@ describe("TelemetryFeed", () => {
       instance = new TelemetryFeed();
       sandbox.stub(instance.utEvents, "sendUserEvent");
 
       await instance.sendUTEvent(event, instance.utEvents.sendUserEvent);
 
       assert.calledWith(instance.utEvents.sendUserEvent, event);
     });
   });
+  describe("#sendStructuredIngestionEvent", () => {
+    it("should call PingCentre sendStructuredIngestionPing", async () => {
+      FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
+      FakePrefs.prototype.prefs[STRUCTURED_INGESTION_TELEMETRY_PREF] = true;
+      const event = {};
+      instance = new TelemetryFeed();
+      sandbox.stub(instance.pingCentre, "sendStructuredIngestionPing");
+
+      await instance.sendStructuredIngestionEvent(event, "http://foo.com/base/");
+
+      assert.calledWith(instance.pingCentre.sendStructuredIngestionPing, event);
+    });
+  });
   describe("#sendASRouterEvent", () => {
     it("should call PingCentre for AS Router", async () => {
       FakePrefs.prototype.prefs.telemetry = true;
       const event = {};
       instance = new TelemetryFeed();
       sandbox.stub(instance.pingCentreForASRouter, "sendPing");
 
       instance.sendASRouterEvent(event);
@@ -902,44 +932,16 @@ describe("TelemetryFeed", () => {
     });
     it("should call .pingCentreForASRouter.uninit", () => {
       const stub = sandbox.stub(instance.pingCentreForASRouter, "uninit");
 
       instance.uninit();
 
       assert.calledOnce(stub);
     });
-    it("should remove the a-s telemetry pref listener", () => {
-      FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
-      instance = new TelemetryFeed();
-
-      assert.property(instance._prefs.observers, TELEMETRY_PREF);
-
-      instance.uninit();
-
-      assert.notProperty(instance._prefs.observers, TELEMETRY_PREF);
-    });
-    it("should remove the a-s ut telemetry pref listener", () => {
-      FakePrefs.prototype.prefs[EVENTS_TELEMETRY_PREF] = true;
-      instance = new TelemetryFeed();
-
-      assert.property(instance._prefs.observers, EVENTS_TELEMETRY_PREF);
-
-      instance.uninit();
-
-      assert.notProperty(instance._prefs.observers, EVENTS_TELEMETRY_PREF);
-    });
-    it("should call Cu.reportError if this._prefs.ignore throws", () => {
-      globals.sandbox.stub(FakePrefs.prototype, "ignore").throws("Some Error");
-      instance = new TelemetryFeed();
-
-      instance.uninit();
-
-      assert.called(global.Cu.reportError);
-    });
     it("should make this.browserOpenNewtabStart() stop observing browser-open-newtab-start and domwindowopened", async () => {
       await instance.init();
       sandbox.spy(Services.obs, "removeObserver");
       sandbox.stub(instance.pingCentre, "uninit");
 
       await instance.uninit();
 
       assert.calledTwice(Services.obs.removeObserver);
@@ -1196,44 +1198,62 @@ describe("TelemetryFeed", () => {
       instance.sendDiscoveryStreamImpressions("foo", session);
 
       assert.notCalled(spy);
     });
     it("should send impression pings if there is impression data", () => {
       const spy = sandbox.spy(instance, "sendEvent");
       const session = {
         impressionSets: {
-          source_foo: [{id: 1}, {id: 2}],
-          source_bar: [{id: 3}, {id: 4}],
+          source_foo: [{id: 1, pos: 0}, {id: 2, pos: 1}],
+          source_bar: [{id: 3, pos: 0}, {id: 4, pos: 1}],
         },
       };
       instance.sendDiscoveryStreamImpressions("foo", session);
 
       assert.calledTwice(spy);
     });
   });
   describe("#handleDiscoveryStreamImpressionStats", () => {
     it("should throw for a missing session", () => {
       assert.throws(() => {
         instance.handleDiscoveryStreamImpressionStats("a_missing_port", {});
       }, "Session does not exist.");
     });
     it("should store impression to impressionSets", () => {
       const session = instance.addSession("new_session", "about:newtab");
-      instance.handleDiscoveryStreamImpressionStats("new_session", {source: "foo", tiles: [{id: 1}]});
+      instance.handleDiscoveryStreamImpressionStats("new_session",
+        {source: "foo", tiles: [{id: 1, pos: 0}]});
 
       assert.equal(Object.keys(session.impressionSets).length, 1);
-      assert.deepEqual(session.impressionSets.foo, [{id: 1}]);
+      assert.deepEqual(session.impressionSets.foo, [{id: 1, pos: 0}]);
 
       // Add another ping with the same source
-      instance.handleDiscoveryStreamImpressionStats("new_session", {source: "foo", tiles: [{id: 2}]});
+      instance.handleDiscoveryStreamImpressionStats("new_session",
+        {source: "foo", tiles: [{id: 2, pos: 1}]});
 
-      assert.deepEqual(session.impressionSets.foo, [{id: 1}, {id: 2}]);
+      assert.deepEqual(session.impressionSets.foo,
+        [{id: 1, pos: 0}, {id: 2, pos: 1}]);
 
       // Add another ping with a different source
-      instance.handleDiscoveryStreamImpressionStats("new_session", {source: "bar", tiles: [{id: 3}]});
+      instance.handleDiscoveryStreamImpressionStats("new_session",
+        {source: "bar", tiles: [{id: 3, pos: 2}]});
 
       assert.equal(Object.keys(session.impressionSets).length, 2);
-      assert.deepEqual(session.impressionSets.foo, [{id: 1}, {id: 2}]);
-      assert.deepEqual(session.impressionSets.bar, [{id: 3}]);
+      assert.deepEqual(session.impressionSets.foo, [{id: 1, pos: 0}, {id: 2, pos: 1}]);
+      assert.deepEqual(session.impressionSets.bar, [{id: 3, pos: 2}]);
+    });
+  });
+  describe("#_generateStructuredIngestionEndpoint", () => {
+    it("should generate a valid endpoint", () => {
+      const fakeEndpoint = "http://fakeendpoint.com/base/";
+      const fakeUUID = "{34f24486-f01a-9749-9c5b-21476af1fa77}";
+      const fakeUUIDWithoutBraces = fakeUUID.substring(1, fakeUUID.length - 1);
+      FakePrefs.prototype.prefs = {};
+      FakePrefs.prototype.prefs[STRUCTURED_INGESTION_ENDPOINT_PREF] = fakeEndpoint;
+      sandbox.stub(global.gUUIDGenerator, "generateUUID").returns(fakeUUID);
+      const feed = new TelemetryFeed();
+      const url = feed._generateStructuredIngestionEndpoint("testPingType", "1");
+
+      assert.equal(url, `${fakeEndpoint}/testPingType/1/${fakeUUIDWithoutBraces}`);
     });
   });
 });
--- a/browser/components/newtab/test/unit/lib/TopStoriesFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/TopStoriesFeed.test.js
@@ -602,32 +602,46 @@ describe("Top Stories Feed", () => {
       sinon.stub(instance, "PersonalityProvider");
       instance.PersonalityProvider = () => ({init: sinon.stub()});
 
       const provider = instance.affinityProividerSwitcher();
       assert.calledOnce(provider.init);
       assert.notCalled(instance.UserDomainAffinityProvider);
     });
     it("should use init and callback from affinityProividerSwitcher using v2", async () => {
+      const stories = {recommendations: {}};
       sinon.stub(instance, "doContentUpdate");
-      sinon.stub(instance, "rotate");
+      sinon.stub(instance, "rotate").returns(stories);
       sinon.stub(instance, "transform");
-      const stories = {recommendations: {}};
       instance.cache.get = () => ({stories});
       instance.cache.set = sinon.spy();
       instance.affinityProvider = {getAffinities: () => ({})};
       await instance.onPersonalityProviderInit();
 
       assert.calledOnce(instance.doContentUpdate);
+      assert.calledWith(instance.doContentUpdate, {stories: {recommendations: {}}}, false);
       assert.calledOnce(instance.rotate);
       assert.calledOnce(instance.transform);
       const {args} = instance.cache.set.firstCall;
       assert.equal(args[0], "domainAffinities");
       assert.equal(args[1]._timestamp, 0);
     });
+    it("should call dispatchUpdateEvent from affinityProividerSwitcher using v2", async () => {
+      const stories = {recommendations: {}};
+      sinon.stub(instance, "rotate").returns(stories);
+      sinon.stub(instance, "transform");
+      sinon.spy(instance, "dispatchUpdateEvent");
+      instance.cache.get = () => ({stories});
+      instance.cache.set = sinon.spy();
+      instance.affinityProvider = {getAffinities: () => ({})};
+
+      await instance.onPersonalityProviderInit();
+
+      assert.calledOnce(instance.dispatchUpdateEvent);
+    });
     it("should return an object for UserDomainAffinityProvider", () => {
       assert.equal(typeof instance.UserDomainAffinityProvider(), "object");
     });
     it("should return an object for PersonalityProvider", () => {
       assert.equal(typeof instance.PersonalityProvider(), "object");
     });
     it("should call affinityProividerSwitcher on loadCachedData", async () => {
       instance.affinityProviderV2 = true;
@@ -1173,29 +1187,28 @@ describe("Top Stories Feed", () => {
 
       clock.tick(TOPICS_UPDATE_TIME);
       await instance.onAction({type: at.SYSTEM_TICK});
       assert.calledOnce(instance.fetchTopics);
     });
     it("should return updated stories and topics on system tick", async () => {
       await instance.onInit();
       sinon.spy(instance, "dispatchUpdateEvent");
-      instance.stories = [{"guid": "rec1"}, {"guid": "rec2"}, {"guid": "rec3"}];
-      instance.topics = {
-        "_timestamp": 123,
-        "topics": [{"name": "topic1", "url": "url-topic1"}, {"name": "topic2", "url": "url-topic2"}],
-      };
+      const stories = [{"guid": "rec1"}, {"guid": "rec2"}, {"guid": "rec3"}];
+      const topics = [{"name": "topic1", "url": "url-topic1"}, {"name": "topic2", "url": "url-topic2"}];
+      clock.tick(TOPICS_UPDATE_TIME);
+      globals.sandbox.stub(instance, "fetchStories").resolves(stories);
+      globals.sandbox.stub(instance, "fetchTopics").resolves(topics);
+
       await instance.onAction({type: at.SYSTEM_TICK});
+
       assert.calledOnce(instance.dispatchUpdateEvent);
       assert.calledWith(instance.dispatchUpdateEvent, false, {
         rows: [{"guid": "rec1"}, {"guid": "rec2"}, {"guid": "rec3"}],
-        topics: {
-          _timestamp: 123,
-          topics: [{"name": "topic1", "url": "url-topic1"}, {"name": "topic2", "url": "url-topic2"}],
-        },
+        topics: [{"name": "topic1", "url": "url-topic1"}, {"name": "topic2", "url": "url-topic2"}],
         read_more_endpoint: undefined,
       });
     });
     it("should update domain affinities on idle-daily, if personalization preffed on", async () => {
       instance.init();
       instance.affinityProvider = undefined;
       instance.cache.set = sinon.spy();
 
@@ -1335,26 +1348,58 @@ describe("Top Stories Feed", () => {
       assert.calledWith(sectionsManagerStub.updateSection, SECTION_ID, {rows: transformedStories, topics: topics.topics, read_more_endpoint: undefined});
     });
     it("should NOT update section if there is no cached data", async () => {
       instance.cache.get = () => ({});
       globals.set("NewTabUtils", {blockedLinks: {isBlocked: globals.sandbox.spy()}});
       await instance.loadCachedData();
       assert.notCalled(sectionsManagerStub.updateSection);
     });
+    it("should use store rows if no stories sent to doContentUpdate", async () => {
+      instance.store = {
+        getState() {
+          return {
+            Sections: [{id: "topstories", rows: [1, 2, 3]}],
+          };
+        },
+      };
+      sinon.spy(instance, "dispatchUpdateEvent");
+
+      instance.doContentUpdate({}, false);
+
+      assert.calledOnce(instance.dispatchUpdateEvent);
+      assert.calledWith(instance.dispatchUpdateEvent, false, {rows: [1, 2, 3]});
+    });
     it("should broadcast in doContentUpdate when updating from cache", async () => {
       sectionsManagerStub.sections.set("topstories", {options: {stories_referrer: "referrer"}});
       globals.set("NewTabUtils", {blockedLinks: {isBlocked: () => {}}});
       const stories = {"recommendations": [{}]};
       const topics = {"topics": [{}]};
       sinon.spy(instance, "doContentUpdate");
       instance.cache.get = () => ({stories, topics});
       await instance.onInit();
       assert.calledOnce(instance.doContentUpdate);
-      assert.calledWith(instance.doContentUpdate, true);
+      assert.calledWith(instance.doContentUpdate, {
+        stories: [{
+          context: undefined,
+          description: undefined,
+          guid: undefined,
+          hostname: undefined,
+          icon: undefined,
+          image: undefined,
+          min_score: 0,
+          referrer: "referrer",
+          score: 1,
+          spoc_meta: {  },
+          title: undefined,
+          type: "trending",
+          url: undefined,
+        }],
+        topics: [{}],
+      }, true);
     });
     it("should initialize user domain affinity provider from cache if personalization is preffed on", async () => {
       const domainAffinities = {
         "parameterSets": {
           "default": {
             "recencyFactor": 0.4,
             "frequencyFactor": 0.5,
             "combinedDomainFactor": 0.5,
--- a/browser/components/newtab/test/unit/unit-entry.js
+++ b/browser/components/newtab/test/unit/unit-entry.js
@@ -82,16 +82,17 @@ const TEST_GLOBAL = {
     },
     "@mozilla.org/updates/update-checker;1": {createInstance() {}},
   },
   Ci: {
     nsICryptoHash: {},
     nsIHttpChannel: {REFERRER_POLICY_UNSAFE_URL: 5},
     nsITimer: {TYPE_ONE_SHOT: 1},
     nsIWebProgressListener: {LOCATION_CHANGE_SAME_DOCUMENT: 1},
+    nsIDOMWindow: Object,
   },
   Cu: {
     importGlobalProperties() {},
     now: () => window.performance.now(),
     reportError() {},
   },
   dump() {},
   fetch() {},
--- a/browser/components/newtab/vendor/REACT_AND_REACT_DOM_LICENSE
+++ b/browser/components/newtab/vendor/REACT_AND_REACT_DOM_LICENSE
@@ -1,11 +1,11 @@
 MIT License
 
-Copyright (c) 2013-present, Facebook, Inc.
+Copyright (c) Facebook, Inc. and its affiliates.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
--- a/browser/components/newtab/vendor/react-dev.js
+++ b/browser/components/newtab/vendor/react-dev.js
@@ -1,25 +1,61 @@
-/** @license React v16.2.0
- * react.development.js for activity-stream
+/** @license React v16.8.3
+ * react.development.js
  *
- * Copyright (c) 2013-present, Facebook, Inc.
+ * Copyright (c) Facebook, Inc. and its affiliates.
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
 
 'use strict';
 
 (function (global, factory) {
 	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
 	typeof define === 'function' && define.amd ? define(factory) :
 	(global.React = factory());
 }(this, (function () { 'use strict';
 
+// TODO: this is special because it gets imported during build.
+
+var ReactVersion = '16.8.3';
+
+// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
+// nor polyfill, then a plain number is used for performance.
+var hasSymbol = typeof Symbol === 'function' && Symbol.for;
+
+var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
+var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca;
+var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb;
+var REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc;
+var REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2;
+var REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd;
+var REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace;
+
+var REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf;
+var REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
+var REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1;
+var REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3;
+var REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;
+
+var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
+var FAUX_ITERATOR_SYMBOL = '@@iterator';
+
+function getIteratorFn(maybeIterable) {
+  if (maybeIterable === null || typeof maybeIterable !== 'object') {
+    return null;
+  }
+  var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
+  if (typeof maybeIterator === 'function') {
+    return maybeIterator;
+  }
+  return null;
+}
+
 /*
 object-assign
 (c) Sindre Sorhus
 @license MIT
 */
 
 
 /* eslint-disable no-unused-vars */
@@ -101,122 +137,60 @@ var objectAssign = shouldUseNative() ? O
 				}
 			}
 		}
 	}
 
 	return to;
 };
 
-// TODO: this is special because it gets imported during build.
-
-var ReactVersion = '16.2.0';
-
-// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
-// nor polyfill, then a plain number is used for performance.
-var hasSymbol = typeof Symbol === 'function' && Symbol['for'];
-
-var REACT_ELEMENT_TYPE = hasSymbol ? Symbol['for']('react.element') : 0xeac7;
-var REACT_CALL_TYPE = hasSymbol ? Symbol['for']('react.call') : 0xeac8;
-var REACT_RETURN_TYPE = hasSymbol ? Symbol['for']('react.return') : 0xeac9;
-var REACT_PORTAL_TYPE = hasSymbol ? Symbol['for']('react.portal') : 0xeaca;
-var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol['for']('react.fragment') : 0xeacb;
-
-var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
-var FAUX_ITERATOR_SYMBOL = '@@iterator';
-
-function getIteratorFn(maybeIterable) {
-  if (maybeIterable === null || typeof maybeIterable === 'undefined') {
-    return null;
-  }
-  var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
-  if (typeof maybeIterator === 'function') {
-    return maybeIterator;
-  }
-  return null;
-}
-
-/**
- * WARNING: DO NOT manually require this module.
- * This is a replacement for `invariant(...)` used by the error code system
- * and will _only_ be required by the corresponding babel pass.
- * It always throws.
- */
-
-/**
- * Copyright (c) 2013-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-
-
-var emptyObject = {};
-
-{
-  Object.freeze(emptyObject);
-}
-
-var emptyObject_1 = emptyObject;
-
-/**
- * Copyright (c) 2013-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-
-
 /**
  * Use invariant() to assert state which your program assumes to be true.
  *
  * Provide sprintf-style format (only %s is supported) and arguments
  * to provide information about what broke and what you were
  * expecting.
  *
  * The invariant message will be stripped in production, but the invariant
  * will remain to ensure logic does not differ in production.
  */
 
-var validateFormat = function validateFormat(format) {};
+var validateFormat = function () {};
 
 {
-  validateFormat = function validateFormat(format) {
+  validateFormat = function (format) {
     if (format === undefined) {
       throw new Error('invariant requires an error message argument');
     }
   };
 }
 
 function invariant(condition, format, a, b, c, d, e, f) {
   validateFormat(format);
 
   if (!condition) {
-    var error;
+    var error = void 0;
     if (format === undefined) {
       error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');
     } else {
       var args = [a, b, c, d, e, f];
       var argIndex = 0;
       error = new Error(format.replace(/%s/g, function () {
         return args[argIndex++];
       }));
       error.name = 'Invariant Violation';
     }
 
     error.framesToPop = 1; // we don't care about invariant's own frame
     throw error;
   }
 }
 
-var invariant_1 = invariant;
+// Relying on the `invariant()` implementation lets us
+// preserve the format and params in the www builds.
 
 /**
  * Forked from fbjs/warning:
  * https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/__forks__/warning.js
  *
  * Only change is we use console.warn instead of console.error,
  * and do nothing when 'console' is not supported.
  * This really simplifies the code.
@@ -247,139 +221,91 @@ var lowPriorityWarning = function () {};
       // This error was thrown as a convenience so that you can use this stack
       // to find the callsite that caused this warning to fire.
       throw new Error(message);
     } catch (x) {}
   };
 
   lowPriorityWarning = function (condition, format) {
     if (format === undefined) {
-      throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
+      throw new Error('`lowPriorityWarning(condition, format, ...args)` requires a warning ' + 'message argument');
     }
     if (!condition) {
       for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
         args[_key2 - 2] = arguments[_key2];
       }
 
       printWarning.apply(undefined, [format].concat(args));
     }
   };
 }
 
 var lowPriorityWarning$1 = lowPriorityWarning;
 
 /**
- * Copyright (c) 2013-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * 
- */
-
-function makeEmptyFunction(arg) {
-  return function () {
-    return arg;
-  };
-}
-
-/**
- * This function accepts and discards inputs; it has no side effects. This is
- * primarily useful idiomatically for overridable function endpoints which
- * always need to be callable, since JS lacks a null-call idiom ala Cocoa.
- */
-var emptyFunction = function emptyFunction() {};
-
-emptyFunction.thatReturns = makeEmptyFunction;
-emptyFunction.thatReturnsFalse = makeEmptyFunction(false);
-emptyFunction.thatReturnsTrue = makeEmptyFunction(true);
-emptyFunction.thatReturnsNull = makeEmptyFunction(null);
-emptyFunction.thatReturnsThis = function () {
-  return this;
-};
-emptyFunction.thatReturnsArgument = function (arg) {
-  return arg;
-};
-
-var emptyFunction_1 = emptyFunction;
-
-/**
- * Copyright (c) 2014-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-
-
-
-
-/**
  * Similar to invariant but only logs a warning if the condition is not met.
  * This can be used to log issues in development environments in critical
  * paths. Removing the logging code for production environments will keep the
  * same logic and follow the same code paths.
  */
 
-var warning = emptyFunction_1;
+var warningWithoutStack = function () {};
 
 {
-  var printWarning$1 = function printWarning(format) {
-    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
-      args[_key - 1] = arguments[_key];
+  warningWithoutStack = function (condition, format) {
+    for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+      args[_key - 2] = arguments[_key];
+    }
+
+    if (format === undefined) {
+      throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning ' + 'message argument');
+    }
+    if (args.length > 8) {
+      // Check before the condition to catch violations early.
+      throw new Error('warningWithoutStack() currently supports at most 8 arguments.');
     }
-
-    var argIndex = 0;
-    var message = 'Warning: ' + format.replace(/%s/g, function () {
-      return args[argIndex++];
-    });
+    if (condition) {
+      return;
+    }
     if (typeof console !== 'undefined') {
-      console.error(message);
+      var argsWithFormat = args.map(function (item) {
+        return '' + item;
+      });
+      argsWithFormat.unshift('Warning: ' + format);
+
+      // We intentionally don't use spread (or .apply) directly because it
+      // breaks IE9: https://github.com/facebook/react/issues/13610
+      Function.prototype.apply.call(console.error, console, argsWithFormat);
     }
     try {
       // --- Welcome to debugging React ---
       // This error was thrown as a convenience so that you can use this stack
       // to find the callsite that caused this warning to fire.
+      var argIndex = 0;
+      var message = 'Warning: ' + format.replace(/%s/g, function () {
+        return args[argIndex++];
+      });
       throw new Error(message);
     } catch (x) {}
   };
-
-  warning = function warning(condition, format) {
-    if (format === undefined) {
-      throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
-    }
-
-    if (format.indexOf('Failed Composite propType: ') === 0) {
-      return; // Ignore CompositeComponent proptype check.
-    }
-
-    if (!condition) {
-      for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
-        args[_key2 - 2] = arguments[_key2];
-      }
-
-      printWarning$1.apply(undefined, [format].concat(args));
-    }
-  };
 }
 
-var warning_1 = warning;
+var warningWithoutStack$1 = warningWithoutStack;
 
 var didWarnStateUpdateForUnmountedComponent = {};
 
 function warnNoop(publicInstance, callerName) {
   {
-    var constructor = publicInstance.constructor;
-    var componentName = constructor && (constructor.displayName || constructor.name) || 'ReactClass';
+    var _constructor = publicInstance.constructor;
+    var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass';
     var warningKey = componentName + '.' + callerName;
     if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
       return;
     }
-    warning_1(false, '%s(...): Can only update a mounted or mounting component. ' + 'This usually means you called %s() on an unmounted component. ' + 'This is a no-op.\n\nPlease check the code for the %s component.', callerName, callerName, componentName);
+    warningWithoutStack$1(false, "Can't call %s on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName);
     didWarnStateUpdateForUnmountedComponent[warningKey] = true;
   }
 }
 
 /**
  * This is the abstract API for an update queue.
  */
 var ReactNoopUpdateQueue = {
@@ -442,23 +368,29 @@ var ReactNoopUpdateQueue = {
    * @param {?string} Name of the calling function in the public API.
    * @internal
    */
   enqueueSetState: function (publicInstance, partialState, callback, callerName) {
     warnNoop(publicInstance, 'setState');
   }
 };
 
+var emptyObject = {};
+{
+  Object.freeze(emptyObject);
+}
+
 /**
  * Base class helpers for the updating state of a component.
  */
 function Component(props, context, updater) {
   this.props = props;
   this.context = context;
-  this.refs = emptyObject_1;
+  // If a component has string refs, we will assign a different object later.
+  this.refs = emptyObject;
   // We initialize the default updater but the real one gets injected by the
   // renderer.
   this.updater = updater || ReactNoopUpdateQueue;
 }
 
 Component.prototype.isReactComponent = {};
 
 /**
@@ -482,17 +414,17 @@ Component.prototype.isReactComponent = {
  *
  * @param {object|function} partialState Next partial state or function to
  *        produce next partial state to be merged with current state.
  * @param {?function} callback Called after state is updated.
  * @final
  * @protected
  */
 Component.prototype.setState = function (partialState, callback) {
-  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant_1(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
+  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
   this.updater.enqueueSetState(this, partialState, callback, 'setState');
 };
 
 /**
  * Forces an update. This should only be invoked when it is known with
  * certainty that we are **not** in a DOM transaction.
  *
  * You may want to call this when you know that some deeper aspect of the
@@ -529,81 +461,1355 @@ Component.prototype.forceUpdate = functi
   };
   for (var fnName in deprecatedAPIs) {
     if (deprecatedAPIs.hasOwnProperty(fnName)) {
       defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
     }
   }
 }
 
+function ComponentDummy() {}
+ComponentDummy.prototype = Component.prototype;
+
 /**
- * Base class helpers for the updating state of a component.
+ * Convenience component with default shallow equality check for sCU.
  */
 function PureComponent(props, context, updater) {
-  // Duplicated from Component.
   this.props = props;
   this.context = context;
-  this.refs = emptyObject_1;
-  // We initialize the default updater but the real one gets injected by the
-  // renderer.
+  // If a component has string refs, we will assign a different object later.
+  this.refs = emptyObject;
   this.updater = updater || ReactNoopUpdateQueue;
 }
 
-function ComponentDummy() {}
-ComponentDummy.prototype = Component.prototype;
 var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();
 pureComponentPrototype.constructor = PureComponent;
 // Avoid an extra prototype jump for these methods.
 objectAssign(pureComponentPrototype, Component.prototype);
 pureComponentPrototype.isPureReactComponent = true;
 
-function AsyncComponent(props, context, updater) {
-  // Duplicated from Component.
-  this.props = props;
-  this.context = context;
-  this.refs = emptyObject_1;
-  // We initialize the default updater but the real one gets injected by the
-  // renderer.
-  this.updater = updater || ReactNoopUpdateQueue;
+// an immutable object with a single mutable value
+function createRef() {
+  var refObject = {
+    current: null
+  };
+  {
+    Object.seal(refObject);
+  }
+  return refObject;
+}
+
+var enableSchedulerDebugging = false;
+
+/* eslint-disable no-var */
+
+// TODO: Use symbols?
+var ImmediatePriority = 1;
+var UserBlockingPriority = 2;
+var NormalPriority = 3;
+var LowPriority = 4;
+var IdlePriority = 5;
+
+// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
+// Math.pow(2, 30) - 1
+// 0b111111111111111111111111111111
+var maxSigned31BitInt = 1073741823;
+
+// Times out immediately
+var IMMEDIATE_PRIORITY_TIMEOUT = -1;
+// Eventually times out
+var USER_BLOCKING_PRIORITY = 250;
+var NORMAL_PRIORITY_TIMEOUT = 5000;
+var LOW_PRIORITY_TIMEOUT = 10000;
+// Never times out
+var IDLE_PRIORITY = maxSigned31BitInt;
+
+// Callbacks are stored as a circular, doubly linked list.
+var firstCallbackNode = null;
+
+var currentDidTimeout = false;
+// Pausing the scheduler is useful for debugging.
+var isSchedulerPaused = false;
+
+var currentPriorityLevel = NormalPriority;
+var currentEventStartTime = -1;
+var currentExpirationTime = -1;
+
+// This is set when a callback is being executed, to prevent re-entrancy.
+var isExecutingCallback = false;
+
+var isHostCallbackScheduled = false;
+
+var hasNativePerformanceNow = typeof performance === 'object' && typeof performance.now === 'function';
+
+function ensureHostCallbackIsScheduled() {
+  if (isExecutingCallback) {
+    // Don't schedule work yet; wait until the next time we yield.
+    return;
+  }
+  // Schedule the host callback using the earliest expiration in the list.
+  var expirationTime = firstCallbackNode.expirationTime;
+  if (!isHostCallbackScheduled) {
+    isHostCallbackScheduled = true;
+  } else {
+    // Cancel the existing host callback.
+    cancelHostCallback();
+  }
+  requestHostCallback(flushWork, expirationTime);
+}
+
+function flushFirstCallback() {
+  var flushedNode = firstCallbackNode;
+
+  // Remove the node from the list before calling the callback. That way the
+  // list is in a consistent state even if the callback throws.
+  var next = firstCallbackNode.next;
+  if (firstCallbackNode === next) {
+    // This is the last callback in the list.
+    firstCallbackNode = null;
+    next = null;
+  } else {
+    var lastCallbackNode = firstCallbackNode.previous;
+    firstCallbackNode = lastCallbackNode.next = next;
+    next.previous = lastCallbackNode;
+  }
+
+  flushedNode.next = flushedNode.previous = null;
+
+  // Now it's safe to call the callback.
+  var callback = flushedNode.callback;
+  var expirationTime = flushedNode.expirationTime;
+  var priorityLevel = flushedNode.priorityLevel;
+  var previousPriorityLevel = currentPriorityLevel;
+  var previousExpirationTime = currentExpirationTime;
+  currentPriorityLevel = priorityLevel;
+  currentExpirationTime = expirationTime;
+  var continuationCallback;
+  try {
+    continuationCallback = callback();
+  } finally {
+    currentPriorityLevel = previousPriorityLevel;
+    currentExpirationTime = previousExpirationTime;
+  }
+
+  // A callback may return a continuation. The continuation should be scheduled
+  // with the same priority and expiration as the just-finished callback.
+  if (typeof continuationCallback === 'function') {
+    var continuationNode = {
+      callback: continuationCallback,
+      priorityLevel: priorityLevel,
+      expirationTime: expirationTime,
+      next: null,
+      previous: null
+    };
+
+    // Insert the new callback into the list, sorted by its expiration. This is
+    // almost the same as the code in `scheduleCallback`, except the callback
+    // is inserted into the list *before* callbacks of equal expiration instead
+    // of after.
+    if (firstCallbackNode === null) {
+      // This is the first callback in the list.
+      firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode;
+    } else {
+      var nextAfterContinuation = null;
+      var node = firstCallbackNode;
+      do {
+        if (node.expirationTime >= expirationTime) {
+          // This callback expires at or after the continuation. We will insert
+          // the continuation *before* this callback.
+          nextAfterContinuation = node;
+          break;
+        }
+        node = node.next;
+      } while (node !== firstCallbackNode);
+
+      if (nextAfterContinuation === null) {
+        // No equal or lower priority callback was found, which means the new
+        // callback is the lowest priority callback in the list.
+        nextAfterContinuation = firstCallbackNode;
+      } else if (nextAfterContinuation === firstCallbackNode) {
+        // The new callback is the highest priority callback in the list.
+        firstCallbackNode = continuationNode;
+        ensureHostCallbackIsScheduled();
+      }
+
+      var previous = nextAfterContinuation.previous;
+      previous.next = nextAfterContinuation.previous = continuationNode;
+      continuationNode.next = nextAfterContinuation;
+      continuationNode.previous = previous;
+    }
+  }
+}
+
+function flushImmediateWork() {
+  if (
+  // Confirm we've exited the outer most event handler
+  currentEventStartTime === -1 && firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority) {
+    isExecutingCallback = true;
+    try {
+      do {
+        flushFirstCallback();
+      } while (
+      // Keep flushing until there are no more immediate callbacks
+      firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority);
+    } finally {
+      isExecutingCallback = false;
+      if (firstCallbackNode !== null) {
+        // There's still work remaining. Request another callback.
+        ensureHostCallbackIsScheduled();
+      } else {
+        isHostCallbackScheduled = false;
+      }
+    }
+  }
+}
+
+function flushWork(didTimeout) {
+  // Exit right away if we're currently paused
+
+  if (enableSchedulerDebugging && isSchedulerPaused) {
+    return;
+  }
+
+  isExecutingCallback = true;
+  var previousDidTimeout = currentDidTimeout;
+  currentDidTimeout = didTimeout;
+  try {
+    if (didTimeout) {
+      // Flush all the expired callbacks without yielding.
+      while (firstCallbackNode !== null && !(enableSchedulerDebugging && isSchedulerPaused)) {
+        // TODO Wrap in feature flag
+        // Read the current time. Flush all the callbacks that expire at or
+        // earlier than that time. Then read the current time again and repeat.
+        // This optimizes for as few performance.now calls as possible.
+        var currentTime = getCurrentTime();
+        if (firstCallbackNode.expirationTime <= currentTime) {
+          do {
+            flushFirstCallback();
+          } while (firstCallbackNode !== null && firstCallbackNode.expirationTime <= currentTime && !(enableSchedulerDebugging && isSchedulerPaused));
+          continue;
+        }
+        break;
+      }
+    } else {
+      // Keep flushing callbacks until we run out of time in the frame.
+      if (firstCallbackNode !== null) {
+        do {
+          if (enableSchedulerDebugging && isSchedulerPaused) {
+            break;
+          }
+          flushFirstCallback();
+        } while (firstCallbackNode !== null && !shouldYieldToHost());
+      }
+    }
+  } finally {
+    isExecutingCallback = false;
+    currentDidTimeout = previousDidTimeout;
+    if (firstCallbackNode !== null) {
+      // There's still work remaining. Request another callback.
+      ensureHostCallbackIsScheduled();
+    } else {
+      isHostCallbackScheduled = false;
+    }
+    // Before exiting, flush all the immediate work that was scheduled.
+    flushImmediateWork();
+  }
+}
+
+function unstable_runWithPriority(priorityLevel, eventHandler) {
+  switch (priorityLevel) {
+    case ImmediatePriority:
+    case UserBlockingPriority:
+    case NormalPriority:
+    case LowPriority:
+    case IdlePriority:
+      break;
+    default:
+      priorityLevel = NormalPriority;
+  }
+
+  var previousPriorityLevel = currentPriorityLevel;
+  var previousEventStartTime = currentEventStartTime;
+  currentPriorityLevel = priorityLevel;
+  currentEventStartTime = getCurrentTime();
+
+  try {
+    return eventHandler();
+  } finally {
+    currentPriorityLevel = previousPriorityLevel;
+    currentEventStartTime = previousEventStartTime;
+
+    // Before exiting, flush all the immediate work that was scheduled.
+    flushImmediateWork();
+  }
+}
+
+function unstable_next(eventHandler) {
+  var priorityLevel = void 0;
+  switch (currentPriorityLevel) {
+    case ImmediatePriority:
+    case UserBlockingPriority:
+    case NormalPriority:
+      // Shift down to normal priority
+      priorityLevel = NormalPriority;
+      break;
+    default:
+      // Anything lower than normal priority should remain at the current level.
+      priorityLevel = currentPriorityLevel;
+      break;
+  }
+
+  var previousPriorityLevel = currentPriorityLevel;
+  var previousEventStartTime = currentEventStartTime;
+  currentPriorityLevel = priorityLevel;
+  currentEventStartTime = getCurrentTime();
+
+  try {
+    return eventHandler();
+  } finally {
+    currentPriorityLevel = previousPriorityLevel;
+    currentEventStartTime = previousEventStartTime;
+
+    // Before exiting, flush all the immediate work that was scheduled.
+    flushImmediateWork();
+  }
+}
+
+function unstable_wrapCallback(callback) {
+  var parentPriorityLevel = currentPriorityLevel;
+  return function () {
+    // This is a fork of runWithPriority, inlined for performance.
+    var previousPriorityLevel = currentPriorityLevel;
+    var previousEventStartTime = currentEventStartTime;
+    currentPriorityLevel = parentPriorityLevel;
+    currentEventStartTime = getCurrentTime();
+
+    try {
+      return callback.apply(this, arguments);
+    } finally {
+      currentPriorityLevel = previousPriorityLevel;
+      currentEventStartTime = previousEventStartTime;
+      flushImmediateWork();
+    }
+  };
+}
+
+function unstable_scheduleCallback(callback, deprecated_options) {
+  var startTime = currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();
+
+  var expirationTime;
+  if (typeof deprecated_options === 'object' && deprecated_options !== null && typeof deprecated_options.timeout === 'number') {
+    // FIXME: Remove this branch once we lift expiration times out of React.
+    expirationTime = startTime + deprecated_options.timeout;
+  } else {
+    switch (currentPriorityLevel) {
+      case ImmediatePriority:
+        expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT;
+        break;
+      case UserBlockingPriority:
+        expirationTime = startTime + USER_BLOCKING_PRIORITY;
+        break;
+      case IdlePriority:
+        expirationTime = startTime + IDLE_PRIORITY;
+        break;
+      case LowPriority:
+        expirationTime = startTime + LOW_PRIORITY_TIMEOUT;
+        break;
+      case NormalPriority:
+      default:
+        expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT;
+    }
+  }
+
+  var newNode = {
+    callback: callback,
+    priorityLevel: currentPriorityLevel,
+    expirationTime: expirationTime,
+    next: null,
+    previous: null
+  };
+
+  // Insert the new callback into the list, ordered first by expiration, then
+  // by insertion. So the new callback is inserted any other callback with
+  // equal expiration.
+  if (firstCallbackNode === null) {
+    // This is the first callback in the list.
+    firstCallbackNode = newNode.next = newNode.previous = newNode;
+    ensureHostCallbackIsScheduled();
+  } else {
+    var next = null;
+    var node = firstCallbackNode;
+    do {
+      if (node.expirationTime > expirationTime) {
+        // The new callback expires before this one.
+        next = node;
+        break;
+      }
+      node = node.next;
+    } while (node !== firstCallbackNode);
+
+    if (next === null) {
+      // No callback with a later expiration was found, which means the new
+      // callback has the latest expiration in the list.
+      next = firstCallbackNode;
+    } else if (next === firstCallbackNode) {
+      // The new callback has the earliest expiration in the entire list.
+      firstCallbackNode = newNode;
+      ensureHostCallbackIsScheduled();
+    }
+
+    var previous = next.previous;
+    previous.next = next.previous = newNode;
+    newNode.next = next;
+    newNode.previous = previous;
+  }
+
+  return newNode;
+}
+
+function unstable_pauseExecution() {
+  isSchedulerPaused = true;
+}
+
+function unstable_continueExecution() {
+  isSchedulerPaused = false;
+  if (firstCallbackNode !== null) {
+    ensureHostCallbackIsScheduled();
+  }
+}
+
+function unstable_getFirstCallbackNode() {
+  return firstCallbackNode;
+}
+
+function unstable_cancelCallback(callbackNode) {
+  var next = callbackNode.next;
+  if (next === null) {
+    // Already cancelled.
+    return;
+  }
+
+  if (next === callbackNode) {
+    // This is the only scheduled callback. Clear the list.
+    firstCallbackNode = null;
+  } else {
+    // Remove the callback from its position in the list.
+    if (callbackNode === firstCallbackNode) {
+      firstCallbackNode = next;
+    }
+    var previous = callbackNode.previous;
+    previous.next = next;
+    next.previous = previous;
+  }
+
+  callbackNode.next = callbackNode.previous = null;
+}
+
+function unstable_getCurrentPriorityLevel() {
+  return currentPriorityLevel;
+}
+
+function unstable_shouldYield() {
+  return !currentDidTimeout && (firstCallbackNode !== null && firstCallbackNode.expirationTime < currentExpirationTime || shouldYieldToHost());
+}
+
+// The remaining code is essentially a polyfill for requestIdleCallback. It
+// works by scheduling a requestAnimationFrame, storing the time for the start
+// of the frame, then scheduling a postMessage which gets scheduled after paint.
+// Within the postMessage handler do as much work as possible until time + frame
+// rate. By separating the idle call into a separate event tick we ensure that
+// layout, paint and other browser work is counted against the available time.
+// The frame rate is dynamically adjusted.
+
+// We capture a local reference to any global, in case it gets polyfilled after
+// this module is initially evaluated. We want to be using a
+// consistent implementation.
+var localDate = Date;
+
+// This initialization code may run even on server environments if a component
+// just imports ReactDOM (e.g. for findDOMNode). Some environments might not
+// have setTimeout or clearTimeout. However, we always expect them to be defined
+// on the client. https://github.com/facebook/react/pull/13088
+var localSetTimeout = typeof setTimeout === 'function' ? setTimeout : undefined;
+var localClearTimeout = typeof clearTimeout === 'function' ? clearTimeout : undefined;
+
+// We don't expect either of these to necessarily be defined, but we will error
+// later if they are missing on the client.
+var localRequestAnimationFrame = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : undefined;
+var localCancelAnimationFrame = typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame : undefined;
+
+var getCurrentTime;
+
+// requestAnimationFrame does not run when the tab is in the background. If
+// we're backgrounded we prefer for that work to happen so that the page
+// continues to load in the background. So we also schedule a 'setTimeout' as
+// a fallback.
+// TODO: Need a better heuristic for backgrounded work.
+var ANIMATION_FRAME_TIMEOUT = 100;
+var rAFID;
+var rAFTimeoutID;
+var requestAnimationFrameWithTimeout = function (callback) {
+  // schedule rAF and also a setTimeout
+  rAFID = localRequestAnimationFrame(function (timestamp) {
+    // cancel the setTimeout
+    localClearTimeout(rAFTimeoutID);
+    callback(timestamp);
+  });
+  rAFTimeoutID = localSetTimeout(function () {
+    // cancel the requestAnimationFrame
+    localCancelAnimationFrame(rAFID);
+    callback(getCurrentTime());
+  }, ANIMATION_FRAME_TIMEOUT);
+};
+
+if (hasNativePerformanceNow) {
+  var Performance = performance;
+  getCurrentTime = function () {
+    return Performance.now();
+  };
+} else {
+  getCurrentTime = function () {
+    return localDate.now();
+  };
+}
+
+var requestHostCallback;
+var cancelHostCallback;
+var shouldYieldToHost;
+
+var globalValue = null;
+if (typeof window !== 'undefined') {
+  globalValue = window;
+} else if (typeof global !== 'undefined') {
+  globalValue = global;
 }
 
-var asyncComponentPrototype = AsyncComponent.prototype = new ComponentDummy();
-asyncComponentPrototype.constructor = AsyncComponent;
-// Avoid an extra prototype jump for these methods.
-objectAssign(asyncComponentPrototype, Component.prototype);
-asyncComponentPrototype.unstable_isAsyncReactComponent = true;
-asyncComponentPrototype.render = function () {
-  return this.props.children;
+if (globalValue && globalValue._schedMock) {
+  // Dynamic injection, only for testing purposes.
+  var globalImpl = globalValue._schedMock;
+  requestHostCallback = globalImpl[0];
+  cancelHostCallback = globalImpl[1];
+  shouldYieldToHost = globalImpl[2];
+  getCurrentTime = globalImpl[3];
+} else if (
+// If Scheduler runs in a non-DOM environment, it falls back to a naive
+// implementation using setTimeout.
+typeof window === 'undefined' ||
+// Check if MessageChannel is supported, too.
+typeof MessageChannel !== 'function') {
+  // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore,
+  // fallback to a naive implementation.
+  var _callback = null;
+  var _flushCallback = function (didTimeout) {
+    if (_callback !== null) {
+      try {
+        _callback(didTimeout);
+      } finally {
+        _callback = null;
+      }
+    }
+  };
+  requestHostCallback = function (cb, ms) {
+    if (_callback !== null) {
+      // Protect against re-entrancy.
+      setTimeout(requestHostCallback, 0, cb);
+    } else {
+      _callback = cb;
+      setTimeout(_flushCallback, 0, false);
+    }
+  };
+  cancelHostCallback = function () {
+    _callback = null;
+  };
+  shouldYieldToHost = function () {
+    return false;
+  };
+} else {
+  if (typeof console !== 'undefined') {
+    // TODO: Remove fb.me link
+    if (typeof localRequestAnimationFrame !== 'function') {
+      console.error("This browser doesn't support requestAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills');
+    }
+    if (typeof localCancelAnimationFrame !== 'function') {
+      console.error("This browser doesn't support cancelAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills');
+    }
+  }
+
+  var scheduledHostCallback = null;
+  var isMessageEventScheduled = false;
+  var timeoutTime = -1;
+
+  var isAnimationFrameScheduled = false;
+
+  var isFlushingHostCallback = false;
+
+  var frameDeadline = 0;
+  // We start out assuming that we run at 30fps but then the heuristic tracking
+  // will adjust this value to a faster fps if we get more frequent animation
+  // frames.
+  var previousFrameTime = 33;
+  var activeFrameTime = 33;
+
+  shouldYieldToHost = function () {
+    return frameDeadline <= getCurrentTime();
+  };
+
+  // We use the postMessage trick to defer idle work until after the repaint.
+  var channel = new MessageChannel();
+  var port = channel.port2;
+  channel.port1.onmessage = function (event) {
+    isMessageEventScheduled = false;
+
+    var prevScheduledCallback = scheduledHostCallback;
+    var prevTimeoutTime = timeoutTime;
+    scheduledHostCallback = null;
+    timeoutTime = -1;
+
+    var currentTime = getCurrentTime();
+
+    var didTimeout = false;
+    if (frameDeadline - currentTime <= 0) {
+      // There's no time left in this idle period. Check if the callback has
+      // a timeout and whether it's been exceeded.
+      if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) {
+        // Exceeded the timeout. Invoke the callback even though there's no
+        // time left.
+        didTimeout = true;
+      } else {
+        // No timeout.
+        if (!isAnimationFrameScheduled) {
+          // Schedule another animation callback so we retry later.
+          isAnimationFrameScheduled = true;
+          requestAnimationFrameWithTimeout(animationTick);
+        }
+        // Exit without invoking the callback.
+        scheduledHostCallback = prevScheduledCallback;
+        timeoutTime = prevTimeoutTime;
+        return;
+      }
+    }
+
+    if (prevScheduledCallback !== null) {
+      isFlushingHostCallback = true;
+      try {
+        prevScheduledCallback(didTimeout);
+      } finally {
+        isFlushingHostCallback = false;
+      }
+    }
+  };
+
+  var animationTick = function (rafTime) {
+    if (scheduledHostCallback !== null) {
+      // Eagerly schedule the next animation callback at the beginning of the
+      // frame. If the scheduler queue is not empty at the end of the frame, it
+      // will continue flushing inside that callback. If the queue *is* empty,
+      // then it will exit immediately. Posting the callback at the start of the
+      // frame ensures it's fired within the earliest possible frame. If we
+      // waited until the end of the frame to post the callback, we risk the
+      // browser skipping a frame and not firing the callback until the frame
+      // after that.
+      requestAnimationFrameWithTimeout(animationTick);
+    } else {
+      // No pending work. Exit.
+      isAnimationFrameScheduled = false;
+      return;
+    }
+
+    var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
+    if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) {
+      if (nextFrameTime < 8) {
+        // Defensive coding. We don't support higher frame rates than 120hz.
+        // If the calculated frame time gets lower than 8, it is probably a bug.
+        nextFrameTime = 8;
+      }
+      // If one frame goes long, then the next one can be short to catch up.
+      // If two frames are short in a row, then that's an indication that we
+      // actually have a higher frame rate than what we're currently optimizing.
+      // We adjust our heuristic dynamically accordingly. For example, if we're
+      // running on 120hz display or 90hz VR display.
+      // Take the max of the two in case one of them was an anomaly due to
+      // missed frame deadlines.
+      activeFrameTime = nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
+    } else {
+      previousFrameTime = nextFrameTime;
+    }
+    frameDeadline = rafTime + activeFrameTime;
+    if (!isMessageEventScheduled) {
+      isMessageEventScheduled = true;
+      port.postMessage(undefined);
+    }
+  };
+
+  requestHostCallback = function (callback, absoluteTimeout) {
+    scheduledHostCallback = callback;
+    timeoutTime = absoluteTimeout;
+    if (isFlushingHostCallback || absoluteTimeout < 0) {
+      // Don't wait for the next frame. Continue working ASAP, in a new event.
+      port.postMessage(undefined);
+    } else if (!isAnimationFrameScheduled) {
+      // If rAF didn't already schedule one, we need to schedule a frame.
+      // TODO: If this rAF doesn't materialize because the browser throttles, we
+      // might want to still have setTimeout trigger rIC as a backup to ensure
+      // that we keep performing work.
+      isAnimationFrameScheduled = true;
+      requestAnimationFrameWithTimeout(animationTick);
+    }
+  };
+
+  cancelHostCallback = function () {
+    scheduledHostCallback = null;
+    isMessageEventScheduled = false;
+    timeoutTime = -1;
+  };
+}
+
+// Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
+
+
+// In some cases, StrictMode should also double-render lifecycles.
+// This can be confusing for tests though,
+// And it can be bad for performance in production.
+// This feature flag can be used to control the behavior:
+
+
+// To preserve the "Pause on caught exceptions" behavior of the debugger, we
+// replay the begin phase of a failed component inside invokeGuardedCallback.
+
+
+// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:
+
+
+// Gather advanced timing metrics for Profiler subtrees.
+
+
+// Trace which interactions trigger each commit.
+var enableSchedulerTracing = true;
+
+// Only used in www builds.
+ // TODO: true? Here it might just be false.
+
+// Only used in www builds.
+
+
+// Only used in www builds.
+
+
+// React Fire: prevent the value and checked attributes from syncing
+// with their related DOM properties
+
+
+// These APIs will no longer be "unstable" in the upcoming 16.7 release,
+// Control this behavior with a flag to support 16.6 minor releases in the meanwhile.
+var enableStableConcurrentModeAPIs = false;
+
+var DEFAULT_THREAD_ID = 0;
+
+// Counters used to generate unique IDs.
+var interactionIDCounter = 0;
+var threadIDCounter = 0;
+
+// Set of currently traced interactions.
+// Interactions "stack"–
+// Meaning that newly traced interactions are appended to the previously active set.
+// When an interaction goes out of scope, the previous set (if any) is restored.
+var interactionsRef = null;
+
+// Listener(s) to notify when interactions begin and end.
+var subscriberRef = null;
+
+if (enableSchedulerTracing) {
+  interactionsRef = {
+    current: new Set()
+  };
+  subscriberRef = {
+    current: null
+  };
+}
+
+function unstable_clear(callback) {
+  if (!enableSchedulerTracing) {
+    return callback();
+  }
+
+  var prevInteractions = interactionsRef.current;
+  interactionsRef.current = new Set();
+
+  try {
+    return callback();
+  } finally {
+    interactionsRef.current = prevInteractions;
+  }
+}
+
+function unstable_getCurrent() {
+  if (!enableSchedulerTracing) {
+    return null;
+  } else {
+    return interactionsRef.current;
+  }
+}
+
+function unstable_getThreadID() {
+  return ++threadIDCounter;
+}
+
+function unstable_trace(name, timestamp, callback) {
+  var threadID = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DEFAULT_THREAD_ID;
+
+  if (!enableSchedulerTracing) {
+    return callback();
+  }
+
+  var interaction = {
+    __count: 1,
+    id: interactionIDCounter++,
+    name: name,
+    timestamp: timestamp
+  };
+
+  var prevInteractions = interactionsRef.current;
+
+  // Traced interactions should stack/accumulate.
+  // To do that, clone the current interactions.
+  // The previous set will be restored upon completion.
+  var interactions = new Set(prevInteractions);
+  interactions.add(interaction);
+  interactionsRef.current = interactions;
+
+  var subscriber = subscriberRef.current;
+  var returnValue = void 0;
+
+  try {
+    if (subscriber !== null) {
+      subscriber.onInteractionTraced(interaction);
+    }
+  } finally {
+    try {
+      if (subscriber !== null) {
+        subscriber.onWorkStarted(interactions, threadID);
+      }
+    } finally {
+      try {
+        returnValue = callback();
+      } finally {
+        interactionsRef.current = prevInteractions;
+
+        try {
+          if (subscriber !== null) {
+            subscriber.onWorkStopped(interactions, threadID);
+          }
+        } finally {
+          interaction.__count--;
+
+          // If no async work was scheduled for this interaction,
+          // Notify subscribers that it's completed.
+          if (subscriber !== null && interaction.__count === 0) {
+            subscriber.onInteractionScheduledWorkCompleted(interaction);
+          }
+        }
+      }
+    }
+  }
+
+  return returnValue;
+}
+
+function unstable_wrap(callback) {
+  var threadID = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_THREAD_ID;
+
+  if (!enableSchedulerTracing) {
+    return callback;
+  }
+
+  var wrappedInteractions = interactionsRef.current;
+
+  var subscriber = subscriberRef.current;
+  if (subscriber !== null) {
+    subscriber.onWorkScheduled(wrappedInteractions, threadID);
+  }
+
+  // Update the pending async work count for the current interactions.
+  // Update after calling subscribers in case of error.
+  wrappedInteractions.forEach(function (interaction) {
+    interaction.__count++;
+  });
+
+  var hasRun = false;
+
+  function wrapped() {
+    var prevInteractions = interactionsRef.current;
+    interactionsRef.current = wrappedInteractions;
+
+    subscriber = subscriberRef.current;
+
+    try {
+      var returnValue = void 0;
+
+      try {
+        if (subscriber !== null) {
+          subscriber.onWorkStarted(wrappedInteractions, threadID);
+        }
+      } finally {
+        try {
+          returnValue = callback.apply(undefined, arguments);
+        } finally {
+          interactionsRef.current = prevInteractions;
+
+          if (subscriber !== null) {
+            subscriber.onWorkStopped(wrappedInteractions, threadID);
+          }
+        }
+      }
+
+      return returnValue;
+    } finally {
+      if (!hasRun) {
+        // We only expect a wrapped function to be executed once,
+        // But in the event that it's executed more than once–
+        // Only decrement the outstanding interaction counts once.
+        hasRun = true;
+
+        // Update pending async counts for all wrapped interactions.
+        // If this was the last scheduled async work for any of them,
+        // Mark them as completed.
+        wrappedInteractions.forEach(function (interaction) {
+          interaction.__count--;
+
+          if (subscriber !== null && interaction.__count === 0) {
+            subscriber.onInteractionScheduledWorkCompleted(interaction);
+          }
+        });
+      }
+    }
+  }
+
+  wrapped.cancel = function cancel() {
+    subscriber = subscriberRef.current;
+
+    try {
+      if (subscriber !== null) {
+        subscriber.onWorkCanceled(wrappedInteractions, threadID);
+      }
+    } finally {
+      // Update pending async counts for all wrapped interactions.
+      // If this was the last scheduled async work for any of them,
+      // Mark them as completed.
+      wrappedInteractions.forEach(function (interaction) {
+        interaction.__count--;
+
+        if (subscriber && interaction.__count === 0) {
+          subscriber.onInteractionScheduledWorkCompleted(interaction);
+        }
+      });
+    }
+  };
+
+  return wrapped;
+}
+
+var subscribers = null;
+if (enableSchedulerTracing) {
+  subscribers = new Set();
+}
+
+function unstable_subscribe(subscriber) {
+  if (enableSchedulerTracing) {
+    subscribers.add(subscriber);
+
+    if (subscribers.size === 1) {
+      subscriberRef.current = {
+        onInteractionScheduledWorkCompleted: onInteractionScheduledWorkCompleted,
+        onInteractionTraced: onInteractionTraced,
+        onWorkCanceled: onWorkCanceled,
+        onWorkScheduled: onWorkScheduled,
+        onWorkStarted: onWorkStarted,
+        onWorkStopped: onWorkStopped
+      };
+    }
+  }
+}
+
+function unstable_unsubscribe(subscriber) {
+  if (enableSchedulerTracing) {
+    subscribers.delete(subscriber);
+
+    if (subscribers.size === 0) {
+      subscriberRef.current = null;
+    }
+  }
+}
+
+function onInteractionTraced(interaction) {
+  var didCatchError = false;
+  var caughtError = null;
+
+  subscribers.forEach(function (subscriber) {
+    try {
+      subscriber.onInteractionTraced(interaction);
+    } catch (error) {
+      if (!didCatchError) {
+        didCatchError = true;
+        caughtError = error;
+      }
+    }
+  });
+
+  if (didCatchError) {
+    throw caughtError;
+  }
+}
+
+function onInteractionScheduledWorkCompleted(interaction) {
+  var didCatchError = false;
+  var caughtError = null;
+
+  subscribers.forEach(function (subscriber) {
+    try {
+      subscriber.onInteractionScheduledWorkCompleted(interaction);
+    } catch (error) {
+      if (!didCatchError) {
+        didCatchError = true;
+        caughtError = error;
+      }
+    }
+  });
+
+  if (didCatchError) {
+    throw caughtError;
+  }
+}
+
+function onWorkScheduled(interactions, threadID) {
+  var didCatchError = false;
+  var caughtError = null;
+
+  subscribers.forEach(function (subscriber) {
+    try {
+      subscriber.onWorkScheduled(interactions, threadID);
+    } catch (error) {
+      if (!didCatchError) {
+        didCatchError = true;
+        caughtError = error;
+      }
+    }
+  });
+
+  if (didCatchError) {
+    throw caughtError;
+  }
+}
+
+function onWorkStarted(interactions, threadID) {
+  var didCatchError = false;
+  var caughtError = null;
+
+  subscribers.forEach(function (subscriber) {
+    try {
+      subscriber.onWorkStarted(interactions, threadID);
+    } catch (error) {
+      if (!didCatchError) {
+        didCatchError = true;
+        caughtError = error;
+      }
+    }
+  });
+
+  if (didCatchError) {
+    throw caughtError;
+  }
+}
+
+function onWorkStopped(interactions, threadID) {
+  var didCatchError = false;
+  var caughtError = null;
+
+  subscribers.forEach(function (subscriber) {
+    try {
+      subscriber.onWorkStopped(interactions, threadID);
+    } catch (error) {
+      if (!didCatchError) {
+        didCatchError = true;
+        caughtError = error;
+      }
+    }
+  });
+
+  if (didCatchError) {
+    throw caughtError;
+  }
+}
+
+function onWorkCanceled(interactions, threadID) {
+  var didCatchError = false;
+  var caughtError = null;
+
+  subscribers.forEach(function (subscriber) {
+    try {
+      subscriber.onWorkCanceled(interactions, threadID);
+    } catch (error) {
+      if (!didCatchError) {
+        didCatchError = true;
+        caughtError = error;
+      }
+    }
+  });
+
+  if (didCatchError) {
+    throw caughtError;
+  }
+}
+
+/**
+ * Keeps track of the current dispatcher.
+ */
+var ReactCurrentDispatcher = {
+  /**
+   * @internal
+   * @type {ReactComponent}
+   */
+  current: null
 };
 
 /**
  * Keeps track of the current owner.
  *
  * The current owner is the component who should own any components that are
  * currently being constructed.
  */
 var ReactCurrentOwner = {
   /**
    * @internal
    * @type {ReactComponent}
    */
   current: null
 };
 
+var BEFORE_SLASH_RE = /^(.*)[\\\/]/;
+
+var describeComponentFrame = function (name, source, ownerName) {
+  var sourceInfo = '';
+  if (source) {
+    var path = source.fileName;
+    var fileName = path.replace(BEFORE_SLASH_RE, '');
+    {
+      // In DEV, include code for a common special case:
+      // prefer "folder/index.js" instead of just "index.js".
+      if (/^index\./.test(fileName)) {
+        var match = path.match(BEFORE_SLASH_RE);
+        if (match) {
+          var pathBeforeSlash = match[1];
+          if (pathBeforeSlash) {
+            var folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
+            fileName = folderName + '/' + fileName;
+          }
+        }
+      }
+    }
+    sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';
+  } else if (ownerName) {
+    sourceInfo = ' (created by ' + ownerName + ')';
+  }
+  return '\n    in ' + (name || 'Unknown') + sourceInfo;
+};
+
+var Resolved = 1;
+
+
+function refineResolvedLazyComponent(lazyComponent) {
+  return lazyComponent._status === Resolved ? lazyComponent._result : null;
+}
+
+function getWrappedName(outerType, innerType, wrapperName) {
+  var functionName = innerType.displayName || innerType.name || '';
+  return outerType.displayName || (functionName !== '' ? wrapperName + '(' + functionName + ')' : wrapperName);
+}
+
+function getComponentName(type) {
+  if (type == null) {
+    // Host root, text node or just invalid type.
+    return null;
+  }
+  {
+    if (typeof type.tag === 'number') {
+      warningWithoutStack$1(false, 'Received an unexpected object in getComponentName(). ' + 'This is likely a bug in React. Please file an issue.');
+    }
+  }
+  if (typeof type === 'function') {
+    return type.displayName || type.name || null;
+  }
+  if (typeof type === 'string') {
+    return type;
+  }
+  switch (type) {
+    case REACT_CONCURRENT_MODE_TYPE:
+      return 'ConcurrentMode';
+    case REACT_FRAGMENT_TYPE:
+      return 'Fragment';
+    case REACT_PORTAL_TYPE:
+      return 'Portal';
+    case REACT_PROFILER_TYPE:
+      return 'Profiler';
+    case REACT_STRICT_MODE_TYPE:
+      return 'StrictMode';
+    case REACT_SUSPENSE_TYPE:
+      return 'Suspense';
+  }
+  if (typeof type === 'object') {
+    switch (type.$$typeof) {
+      case REACT_CONTEXT_TYPE:
+        return 'Context.Consumer';
+      case REACT_PROVIDER_TYPE:
+        return 'Context.Provider';
+      case REACT_FORWARD_REF_TYPE:
+        return getWrappedName(type, type.render, 'ForwardRef');
+      case REACT_MEMO_TYPE:
+        return getComponentName(type.type);
+      case REACT_LAZY_TYPE:
+        {
+          var thenable = type;
+          var resolvedThenable = refineResolvedLazyComponent(thenable);
+          if (resolvedThenable) {
+            return getComponentName(resolvedThenable);
+          }
+        }
+    }
+  }
+  return null;
+}
+
+var ReactDebugCurrentFrame = {};
+
+var currentlyValidatingElement = null;
+
+function setCurrentlyValidatingElement(element) {
+  {
+    currentlyValidatingElement = element;
+  }
+}
+
+{
+  // Stack implementation injected by the current renderer.
+  ReactDebugCurrentFrame.getCurrentStack = null;
+
+  ReactDebugCurrentFrame.getStackAddendum = function () {
+    var stack = '';
+
+    // Add an extra top frame while an element is being validated
+    if (currentlyValidatingElement) {
+      var name = getComponentName(currentlyValidatingElement.type);
+      var owner = currentlyValidatingElement._owner;
+      stack += describeComponentFrame(name, currentlyValidatingElement._source, owner && getComponentName(owner.type));
+    }
+
+    // Delegate to the injected renderer-specific implementation
+    var impl = ReactDebugCurrentFrame.getCurrentStack;
+    if (impl) {
+      stack += impl() || '';
+    }
+
+    return stack;
+  };
+}
+
+var ReactSharedInternals = {
+  ReactCurrentDispatcher: ReactCurrentDispatcher,
+  ReactCurrentOwner: ReactCurrentOwner,
+  // Used by renderers to avoid bundling object-assign twice in UMD bundles:
+  assign: objectAssign
+};
+
+{
+  // Re-export the schedule API(s) for UMD bundles.
+  // This avoids introducing a dependency on a new UMD global in a minor update,
+  // Since that would be a breaking change (e.g. for all existing CodeSandboxes).
+  // This re-export is only required for UMD bundles;
+  // CJS bundles use the shared NPM package.
+  objectAssign(ReactSharedInternals, {
+    Scheduler: {
+      unstable_cancelCallback: unstable_cancelCallback,
+      unstable_shouldYield: unstable_shouldYield,
+      unstable_now: getCurrentTime,
+      unstable_scheduleCallback: unstable_scheduleCallback,
+      unstable_runWithPriority: unstable_runWithPriority,
+      unstable_next: unstable_next,
+      unstable_wrapCallback: unstable_wrapCallback,
+      unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
+      unstable_pauseExecution: unstable_pauseExecution,
+      unstable_continueExecution: unstable_continueExecution,
+      unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
+      unstable_IdlePriority: IdlePriority,
+      unstable_ImmediatePriority: ImmediatePriority,
+      unstable_LowPriority: LowPriority,
+      unstable_NormalPriority: NormalPriority,
+      unstable_UserBlockingPriority: UserBlockingPriority
+    },
+    SchedulerTracing: {
+      __interactionsRef: interactionsRef,
+      __subscriberRef: subscriberRef,
+      unstable_clear: unstable_clear,
+      unstable_getCurrent: unstable_getCurrent,
+      unstable_getThreadID: unstable_getThreadID,
+      unstable_subscribe: unstable_subscribe,
+      unstable_trace: unstable_trace,
+      unstable_unsubscribe: unstable_unsubscribe,
+      unstable_wrap: unstable_wrap
+    }
+  });
+}
+
+{
+  objectAssign(ReactSharedInternals, {
+    // These should not be included in production.
+    ReactDebugCurrentFrame: ReactDebugCurrentFrame,
+    // Shim for React DOM 16.0.0 which still destructured (but not used) this.
+    // TODO: remove in React 17.0.
+    ReactComponentTreeHook: {}
+  });
+}
+
+/**
+ * Similar to invariant but only logs a warning if the condition is not met.
+ * This can be used to log issues in development environments in critical
+ * paths. Removing the logging code for production environments will keep the
+ * same logic and follow the same code paths.
+ */
+
+var warning = warningWithoutStack$1;
+
+{
+  warning = function (condition, format) {
+    if (condition) {
+      return;
+    }
+    var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
+    var stack = ReactDebugCurrentFrame.getStackAddendum();
+    // eslint-disable-next-line react-internal/warning-and-invariant-args
+
+    for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+      args[_key - 2] = arguments[_key];
+    }
+
+    warningWithoutStack$1.apply(undefined, [false, format + '%s'].concat(args, [stack]));
+  };
+}
+
+var warning$1 = warning;
+
 var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
 
 var RESERVED_PROPS = {
   key: true,
   ref: true,
   __self: true,
   __source: true
 };
 
-var specialPropKeyWarningShown;
-var specialPropRefWarningShown;
+var specialPropKeyWarningShown = void 0;
+var specialPropRefWarningShown = void 0;
 
 function hasValidRef(config) {
   {
     if (hasOwnProperty$1.call(config, 'ref')) {
       var getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
       if (getter && getter.isReactWarning) {
         return false;
       }
@@ -623,31 +1829,31 @@ function hasValidKey(config) {
   }
   return config.key !== undefined;
 }
 
 function defineKeyPropWarningGetter(props, displayName) {
   var warnAboutAccessingKey = function () {
     if (!specialPropKeyWarningShown) {
       specialPropKeyWarningShown = true;
-      warning_1(false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);
+      warningWithoutStack$1(false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);
     }
   };
   warnAboutAccessingKey.isReactWarning = true;
   Object.defineProperty(props, 'key', {
     get: warnAboutAccessingKey,
     configurable: true
   });
 }
 
 function defineRefPropWarningGetter(props, displayName) {
   var warnAboutAccessingRef = function () {
     if (!specialPropRefWarningShown) {
       specialPropRefWarningShown = true;
-      warning_1(false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);
+      warningWithoutStack$1(false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName);
     }
   };
   warnAboutAccessingRef.isReactWarning = true;
   Object.defineProperty(props, 'ref', {
     get: warnAboutAccessingRef,
     configurable: true
   });
 }
@@ -669,17 +1875,17 @@ function defineRefPropWarningGetter(prop
  * @param {*} source An annotation object (added by a transpiler or otherwise)
  * indicating filename, line number, and/or other information.
  * @param {*} owner
  * @param {*} props
  * @internal
  */
 var ReactElement = function (type, key, ref, self, source, owner, props) {
   var element = {
-    // This tag allow us to uniquely identify this as a React Element
+    // This tag allows us to uniquely identify this as a React Element
     $$typeof: REACT_ELEMENT_TYPE,
 
     // Built-in properties that belong on the element
     type: type,
     key: key,
     ref: ref,
     props: props,
 
@@ -728,17 +1934,17 @@ var ReactElement = function (type, key, 
   return element;
 };
 
 /**
  * Create and return a new ReactElement of the given type.
  * See https://reactjs.org/docs/react-api.html#createelement
  */
 function createElement(type, config, children) {
-  var propName;
+  var propName = void 0;
 
   // Reserved names are extracted
   var props = {};
 
   var key = null;
   var ref = null;
   var self = null;
   var source = null;
@@ -785,24 +1991,22 @@ function createElement(type, config, chi
     for (propName in defaultProps) {
       if (props[propName] === undefined) {
         props[propName] = defaultProps[propName];
       }
     }
   }
   {
     if (key || ref) {
-      if (typeof props.$$typeof === 'undefined' || props.$$typeof !== REACT_ELEMENT_TYPE) {
-        var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
-        if (key) {
-          defineKeyPropWarningGetter(props, displayName);
-        }
-        if (ref) {
-          defineRefPropWarningGetter(props, displayName);
-        }
+      var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
+      if (key) {
+        defineKeyPropWarningGetter(props, displayName);
+      }
+      if (ref) {
+        defineRefPropWarningGetter(props, displayName);
       }
     }
   }
   return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
 }
 
 /**
  * Return a function that produces ReactElements of a given type.
@@ -816,17 +2020,19 @@ function cloneAndReplaceKey(oldElement, 
   return newElement;
 }
 
 /**
  * Clone and return a new ReactElement using element as the starting point.
  * See https://reactjs.org/docs/react-api.html#cloneelement
  */
 function cloneElement(element, config, children) {
-  var propName;
+  !!(element === null || element === undefined) ? invariant(false, 'React.cloneElement(...): The argument must be a React element, but you passed %s.', element) : void 0;
+
+  var propName = void 0;
 
   // Original props are copied
   var props = objectAssign({}, element.props);
 
   // Reserved names are extracted
   var key = element.key;
   var ref = element.ref;
   // Self is preserved since the owner is preserved.
@@ -845,17 +2051,17 @@ function cloneElement(element, config, c
       ref = config.ref;
       owner = ReactCurrentOwner.current;
     }
     if (hasValidKey(config)) {
       key = '' + config.key;
     }
 
     // Remaining properties override existing props
-    var defaultProps;
+    var defaultProps = void 0;
     if (element.type && element.type.defaultProps) {
       defaultProps = element.type.defaultProps;
     }
     for (propName in config) {
       if (hasOwnProperty$1.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
         if (config[propName] === undefined && defaultProps !== undefined) {
           // Resolve default props
           props[propName] = defaultProps[propName];
@@ -881,38 +2087,23 @@ function cloneElement(element, config, c
 
   return ReactElement(element.type, key, ref, self, source, owner, props);
 }
 
 /**
  * Verifies the object is a ReactElement.
  * See https://reactjs.org/docs/react-api.html#isvalidelement
  * @param {?object} object
- * @return {boolean} True if `object` is a valid component.
+ * @return {boolean} True if `object` is a ReactElement.
  * @final
  */
 function isValidElement(object) {
   return typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
 }
 
-var ReactDebugCurrentFrame = {};
-
-{
-  // Component that is being worked on
-  ReactDebugCurrentFrame.getCurrentStack = null;
-
-  ReactDebugCurrentFrame.getStackAddendum = function () {
-    var impl = ReactDebugCurrentFrame.getCurrentStack;
-    if (impl) {
-      return impl();
-    }
-    return null;
-  };
-}
-
 var SEPARATOR = '.';
 var SUBSEPARATOR = ':';
 
 /**
  * Escape and wrap key so it is safe to use as a reactid
  *
  * @param {string} key to be escaped.
  * @return {string} the escaped key.
@@ -999,69 +2190,67 @@ function traverseAllChildrenImpl(childre
     switch (type) {
       case 'string':
       case 'number':
         invokeCallback = true;
         break;
       case 'object':
         switch (children.$$typeof) {
           case REACT_ELEMENT_TYPE:
-          case REACT_CALL_TYPE:
-          case REACT_RETURN_TYPE:
           case REACT_PORTAL_TYPE:
             invokeCallback = true;
         }
     }
   }
 
   if (invokeCallback) {
     callback(traverseContext, children,
     // If it's the only child, treat the name as if it was wrapped in an array
     // so that it's consistent if the number of children grows.
     nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
     return 1;
   }
 
-  var child;
-  var nextName;
+  var child = void 0;
+  var nextName = void 0;
   var subtreeCount = 0; // Count of children found in the current subtree.
   var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
 
   if (Array.isArray(children)) {
     for (var i = 0; i < children.length; i++) {
       child = children[i];
       nextName = nextNamePrefix + getComponentKey(child, i);
       subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
     }
   } else {
     var iteratorFn = getIteratorFn(children);
     if (typeof iteratorFn === 'function') {
       {
         // Warn about using Maps as children
         if (iteratorFn === children.entries) {
-          warning_1(didWarnAboutMaps, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.%s', ReactDebugCurrentFrame.getStackAddendum());
+          !didWarnAboutMaps ? warning$1(false, 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.') : void 0;
           didWarnAboutMaps = true;
         }
       }
 
       var iterator = iteratorFn.call(children);
-      var step;
+      var step = void 0;
       var ii = 0;
       while (!(step = iterator.next()).done) {
         child = step.value;
         nextName = nextNamePrefix + getComponentKey(child, ii++);
         subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
       }
     } else if (type === 'object') {
       var addendum = '';
       {
         addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + ReactDebugCurrentFrame.getStackAddendum();
       }
       var childrenString = '' + children;
-      invariant_1(false, 'Objects are not valid as a React child (found: %s).%s', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum);
+      invariant(false, 'Objects are not valid as a React child (found: %s).%s', childrenString === '[object Object]' ? 'object with keys {' + Object.keys(children).join(', ') + '}' : childrenString, addendum);
     }
   }
 
   return subtreeCount;
 }
 
 /**
  * Traverses children that are typically specified as `props.children`, but
@@ -1110,17 +2299,17 @@ function forEachSingleChild(bookKeeping,
       context = bookKeeping.context;
 
   func.call(context, child, bookKeeping.count++);
 }
 
 /**
  * Iterates through children that are typically specified as `props.children`.
  *
- * See https://reactjs.org/docs/react-api.html#react.children.foreach
+ * See https://reactjs.org/docs/react-api.html#reactchildrenforeach
  *
  * The provided forEachFunc(child, index) will be called for each
  * leaf child.
  *
  * @param {?*} children Children tree container.
  * @param {function(*, int)} forEachFunc
  * @param {*} forEachContext Context for forEachContext.
  */
@@ -1137,17 +2326,19 @@ function mapSingleChildIntoContext(bookK
   var result = bookKeeping.result,
       keyPrefix = bookKeeping.keyPrefix,
       func = bookKeeping.func,
       context = bookKeeping.context;
 
 
   var mappedChild = func.call(context, child, bookKeeping.count++);
   if (Array.isArray(mappedChild)) {
-    mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, emptyFunction_1.thatReturnsArgument);
+    mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, function (c) {
+      return c;
+    });
   } else if (mappedChild != null) {
     if (isValidElement(mappedChild)) {
       mappedChild = cloneAndReplaceKey(mappedChild,
       // Keep both the (mapped) and old keys if they differ, just as
       // traverseAllChildren used to do for objects as children
       keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) + '/' : '') + childKey);
     }
     result.push(mappedChild);
@@ -1162,17 +2353,17 @@ function mapIntoWithKeyPrefixInternal(ch
   var traverseContext = getPooledTraverseContext(array, escapedPrefix, func, context);
   traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
   releaseTraverseContext(traverseContext);
 }
 
 /**
  * Maps children that are typically specified as `props.children`.
  *
- * See https://reactjs.org/docs/react-api.html#react.children.map
+ * See https://reactjs.org/docs/react-api.html#reactchildrenmap
  *
  * The provided mapFunction(child, key, index) will be called for each
  * leaf child.
  *
  * @param {?*} children Children tree container.
  * @param {function(*, int)} func The map function.
  * @param {*} context Context for mapFunction.
  * @return {object} Object containing the ordered map of results.
@@ -1185,70 +2376,325 @@ function mapChildren(children, func, con
   mapIntoWithKeyPrefixInternal(children, result, null, func, context);
   return result;
 }
 
 /**
  * Count the number of children that are typically specified as
  * `props.children`.
  *
- * See https://reactjs.org/docs/react-api.html#react.children.count
+ * See https://reactjs.org/docs/react-api.html#reactchildrencount
  *
  * @param {?*} children Children tree container.
  * @return {number} The number of children.
  */
-function countChildren(children, context) {
-  return traverseAllChildren(children, emptyFunction_1.thatReturnsNull, null);
+function countChildren(children) {
+  return traverseAllChildren(children, function () {
+    return null;
+  }, null);
 }
 
 /**
  * Flatten a children object (typically specified as `props.children`) and
  * return an array with appropriately re-keyed children.
  *
- * See https://reactjs.org/docs/react-api.html#react.children.toarray
+ * See https://reactjs.org/docs/react-api.html#reactchildrentoarray
  */
 function toArray(children) {
   var result = [];
-  mapIntoWithKeyPrefixInternal(children, result, null, emptyFunction_1.thatReturnsArgument);
+  mapIntoWithKeyPrefixInternal(children, result, null, function (child) {
+    return child;
+  });
   return result;
 }
 
 /**
  * Returns the first child in a collection of children and verifies that there
  * is only one child in the collection.
  *
- * See https://reactjs.org/docs/react-api.html#react.children.only
+ * See https://reactjs.org/docs/react-api.html#reactchildrenonly
  *
  * The current implementation of this function assumes that a single child gets
  * passed without a wrapper, but the purpose of this helper function is to
  * abstract away the particular structure of children.
  *
  * @param {?object} children Child collection structure.
  * @return {ReactElement} The first and only `ReactElement` contained in the
  * structure.
  */
 function onlyChild(children) {
-  !isValidElement(children) ? invariant_1(false, 'React.Children.only expected to receive a single React element child.') : void 0;
+  !isValidElement(children) ? invariant(false, 'React.Children.only expected to receive a single React element child.') : void 0;
   return children;
 }
 
-var describeComponentFrame = function (name, source, ownerName) {
-  return '\n    in ' + (name || 'Unknown') + (source ? ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + source.lineNumber + ')' : ownerName ? ' (created by ' + ownerName + ')' : '');
-};
-
-function getComponentName(fiber) {
-  var type = fiber.type;
-
-  if (typeof type === 'string') {
-    return type;
+function createContext(defaultValue, calculateChangedBits) {
+  if (calculateChangedBits === undefined) {
+    calculateChangedBits = null;
+  } else {
+    {
+      !(calculateChangedBits === null || typeof calculateChangedBits === 'function') ? warningWithoutStack$1(false, 'createContext: Expected the optional second argument to be a ' + 'function. Instead received: %s', calculateChangedBits) : void 0;
+    }
+  }
+
+  var context = {
+    $$typeof: REACT_CONTEXT_TYPE,
+    _calculateChangedBits: calculateChangedBits,
+    // As a workaround to support multiple concurrent renderers, we categorize
+    // some renderers as primary and others as secondary. We only expect
+    // there to be two concurrent renderers at most: React Native (primary) and
+    // Fabric (secondary); React DOM (primary) and React ART (secondary).
+    // Secondary renderers store their context values on separate fields.
+    _currentValue: defaultValue,
+    _currentValue2: defaultValue,
+    // Used to track how many concurrent renderers this context currently
+    // supports within in a single renderer. Such as parallel server rendering.
+    _threadCount: 0,
+    // These are circular
+    Provider: null,
+    Consumer: null
+  };
+
+  context.Provider = {
+    $$typeof: REACT_PROVIDER_TYPE,
+    _context: context
+  };
+
+  var hasWarnedAboutUsingNestedContextConsumers = false;
+  var hasWarnedAboutUsingConsumerProvider = false;
+
+  {
+    // A separate object, but proxies back to the original context object for
+    // backwards compatibility. It has a different $$typeof, so we can properly
+    // warn for the incorrect usage of Context as a Consumer.
+    var Consumer = {
+      $$typeof: REACT_CONTEXT_TYPE,
+      _context: context,
+      _calculateChangedBits: context._calculateChangedBits
+    };
+    // $FlowFixMe: Flow complains about not setting a value, which is intentional here
+    Object.defineProperties(Consumer, {
+      Provider: {
+        get: function () {
+          if (!hasWarnedAboutUsingConsumerProvider) {
+            hasWarnedAboutUsingConsumerProvider = true;
+            warning$1(false, 'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' + 'a future major release. Did you mean to render <Context.Provider> instead?');
+          }
+          return context.Provider;
+        },
+        set: function (_Provider) {
+          context.Provider = _Provider;
+        }
+      },
+      _currentValue: {
+        get: function () {
+          return context._currentValue;
+        },
+        set: function (_currentValue) {
+          context._currentValue = _currentValue;
+        }
+      },
+      _currentValue2: {
+        get: function () {
+          return context._currentValue2;
+        },
+        set: function (_currentValue2) {
+          context._currentValue2 = _currentValue2;
+        }
+      },
+      _threadCount: {
+        get: function () {
+          return context._threadCount;
+        },
+        set: function (_threadCount) {
+          context._threadCount = _threadCount;
+        }
+      },
+      Consumer: {
+        get: function () {
+          if (!hasWarnedAboutUsingNestedContextConsumers) {
+            hasWarnedAboutUsingNestedContextConsumers = true;
+            warning$1(false, 'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' + 'a future major release. Did you mean to render <Context.Consumer> instead?');
+          }
+          return context.Consumer;
+        }
+      }
+    });
+    // $FlowFixMe: Flow complains about missing properties because it doesn't understand defineProperty
+    context.Consumer = Consumer;
+  }
+
+  {
+    context._currentRenderer = null;
+    context._currentRenderer2 = null;
   }
-  if (typeof type === 'function') {
-    return type.displayName || type.name;
+
+  return context;
+}
+
+function lazy(ctor) {
+  var lazyType = {
+    $$typeof: REACT_LAZY_TYPE,
+    _ctor: ctor,
+    // React uses these fields to store the result.
+    _status: -1,
+    _result: null
+  };
+
+  {
+    // In production, this would just set it on the object.
+    var defaultProps = void 0;
+    var propTypes = void 0;
+    Object.defineProperties(lazyType, {
+      defaultProps: {
+        configurable: true,
+        get: function () {
+          return defaultProps;
+        },
+        set: function (newDefaultProps) {
+          warning$1(false, 'React.lazy(...): It is not supported to assign `defaultProps` to ' + 'a lazy component import. Either specify them where the component ' + 'is defined, or create a wrapping component around it.');
+          defaultProps = newDefaultProps;
+          // Match production behavior more closely:
+          Object.defineProperty(lazyType, 'defaultProps', {
+            enumerable: true
+          });
+        }
+      },
+      propTypes: {
+        configurable: true,
+        get: function () {
+          return propTypes;
+        },
+        set: function (newPropTypes) {
+          warning$1(false, 'React.lazy(...): It is not supported to assign `propTypes` to ' + 'a lazy component import. Either specify them where the component ' + 'is defined, or create a wrapping component around it.');
+          propTypes = newPropTypes;
+          // Match production behavior more closely:
+          Object.defineProperty(lazyType, 'propTypes', {
+            enumerable: true
+          });
+        }
+      }
+    });
+  }
+
+  return lazyType;
+}
+
+function forwardRef(render) {
+  {
+    if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
+      warningWithoutStack$1(false, 'forwardRef requires a render function but received a `memo` ' + 'component. Instead of forwardRef(memo(...)), use ' + 'memo(forwardRef(...)).');
+    } else if (typeof render !== 'function') {
+      warningWithoutStack$1(false, 'forwardRef requires a render function but was given %s.', render === null ? 'null' : typeof render);
+    } else {
+      !(
+      // Do not warn for 0 arguments because it could be due to usage of the 'arguments' object
+      render.length === 0 || render.length === 2) ? warningWithoutStack$1(false, 'forwardRef render functions accept exactly two parameters: props and ref. %s', render.length === 1 ? 'Did you forget to use the ref parameter?' : 'Any additional parameter will be undefined.') : void 0;
+    }
+
+    if (render != null) {
+      !(render.defaultProps == null && render.propTypes == null) ? warningWithoutStack$1(false, 'forwardRef render functions do not support propTypes or defaultProps. ' + 'Did you accidentally pass a React component?') : void 0;
+    }
   }
-  return null;
+
+  return {
+    $$typeof: REACT_FORWARD_REF_TYPE,
+    render: render
+  };
+}
+
+function isValidElementType(type) {
+  return typeof type === 'string' || typeof type === 'function' ||
+  // Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
+  type === REACT_FRAGMENT_TYPE || type === REACT_CONCURRENT_MODE_TYPE || type === REACT_PROFILER_TYPE || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || typeof type === 'object' && type !== null && (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE);
+}
+
+function memo(type, compare) {
+  {
+    if (!isValidElementType(type)) {
+      warningWithoutStack$1(false, 'memo: The first argument must be a component. Instead ' + 'received: %s', type === null ? 'null' : typeof type);
+    }
+  }
+  return {
+    $$typeof: REACT_MEMO_TYPE,
+    type: type,
+    compare: compare === undefined ? null : compare
+  };
+}
+
+function resolveDispatcher() {
+  var dispatcher = ReactCurrentDispatcher.current;
+  !(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)') : void 0;
+  return dispatcher;
+}
+
+function useContext(Context, unstable_observedBits) {
+  var dispatcher = resolveDispatcher();
+  {
+    !(unstable_observedBits === undefined) ? warning$1(false, 'useContext() second argument is reserved for future ' + 'use in React. Passing it is not supported. ' + 'You passed: %s.%s', unstable_observedBits, typeof unstable_observedBits === 'number' && Array.isArray(arguments[2]) ? '\n\nDid you call array.map(useContext)? ' + 'Calling Hooks inside a loop is not supported. ' + 'Learn more at https://fb.me/rules-of-hooks' : '') : void 0;
+
+    // TODO: add a more generic warning for invalid values.
+    if (Context._context !== undefined) {
+      var realContext = Context._context;
+      // Don't deduplicate because this legitimately causes bugs
+      // and nobody should be using this in existing code.
+      if (realContext.Consumer === Context) {
+        warning$1(false, 'Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be ' + 'removed in a future major release. Did you mean to call useContext(Context) instead?');
+      } else if (realContext.Provider === Context) {
+        warning$1(false, 'Calling useContext(Context.Provider) is not supported. ' + 'Did you mean to call useContext(Context) instead?');
+      }
+    }
+  }
+  return dispatcher.useContext(Context, unstable_observedBits);
+}
+
+function useState(initialState) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useState(initialState);
+}
+
+function useReducer(reducer, initialArg, init) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useReducer(reducer, initialArg, init);
+}
+
+function useRef(initialValue) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useRef(initialValue);
+}
+
+function useEffect(create, inputs) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useEffect(create, inputs);
+}
+
+function useLayoutEffect(create, inputs) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useLayoutEffect(create, inputs);
+}
+
+function useCallback(callback, inputs) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useCallback(callback, inputs);
+}
+
+function useMemo(create, inputs) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useMemo(create, inputs);
+}
+
+function useImperativeHandle(ref, create, inputs) {
+  var dispatcher = resolveDispatcher();
+  return dispatcher.useImperativeHandle(ref, create, inputs);
+}
+
+function useDebugValue(value, formatterFn) {
+  {
+    var dispatcher = resolveDispatcher();
+    return dispatcher.useDebugValue(value, formatterFn);
+  }
 }
 
 /**
  * Copyright (c) 2013-present, Facebook, Inc.
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
@@ -1263,21 +2709,34 @@ var ReactPropTypesSecret_1 = ReactPropTy
  * Copyright (c) 2013-present, Facebook, Inc.
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
 
 
 
+var printWarning$1 = function() {};
+
 {
-  var invariant$2 = invariant_1;
-  var warning$2 = warning_1;
   var ReactPropTypesSecret = ReactPropTypesSecret_1;
   var loggedTypeFailures = {};
+
+  printWarning$1 = function(text) {
+    var message = 'Warning: ' + text;
+    if (typeof console !== 'undefined') {
+      console.error(message);
+    }
+    try {
+      // --- Welcome to debugging React ---
+      // This error was thrown as a convenience so that you can use this stack
+      // to find the callsite that caused this warning to fire.
+      throw new Error(message);
+    } catch (x) {}
+  };
 }
 
 /**
  * Assert that the values match with the type specs.
  * Error messages are memorized and will only be shown once.
  *
  * @param {object} typeSpecs Map of name to a ReactPropType
  * @param {object} values Runtime values that need to be type-checked
@@ -1292,81 +2751,73 @@ function checkPropTypes(typeSpecs, value
       if (typeSpecs.hasOwnProperty(typeSpecName)) {
         var error;
         // Prop type validation may throw. In case they do, we don't want to
         // fail the render phase where it didn't fail before. So we log it.
         // After these have been cleaned up, we'll let them throw.
         try {
           // This is intentionally an invariant that gets caught. It's the same
           // behavior as without this statement except with a better message.
-          invariant$2(typeof typeSpecs[typeSpecName] === 'function', '%s: %s type `%s` is invalid; it must be a function, usually from ' + 'the `prop-types` package, but received `%s`.', componentName || 'React class', location, typeSpecName, typeof typeSpecs[typeSpecName]);
+          if (typeof typeSpecs[typeSpecName] !== 'function') {
+            var err = Error(
+              (componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' +
+              'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.'
+            );
+            err.name = 'Invariant Violation';
+            throw err;
+          }
           error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);
         } catch (ex) {
           error = ex;
         }
-        warning$2(!error || error instanceof Error, '%s: type specification of %s `%s` is invalid; the type checker ' + 'function must return `null` or an `Error` but returned a %s. ' + 'You may have forgotten to pass an argument to the type checker ' + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + 'shape all require an argument).', componentName || 'React class', location, typeSpecName, typeof error);
+        if (error && !(error instanceof Error)) {
+          printWarning$1(
+            (componentName || 'React class') + ': type specification of ' +
+            location + ' `' + typeSpecName + '` is invalid; the type checker ' +
+            'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +
+            'You may have forgotten to pass an argument to the type checker ' +
+            'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
+            'shape all require an argument).'
+          );
+
+        }
         if (error instanceof Error && !(error.message in loggedTypeFailures)) {
           // Only monitor this failure once because there tends to be a lot of the
           // same error.
           loggedTypeFailures[error.message] = true;
 
           var stack = getStack ? getStack() : '';
 
-          warning$2(false, 'Failed %s type: %s%s', location, error.message, stack != null ? stack : '');
+          printWarning$1(
+            'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '')
+          );
         }
       }
     }
   }
 }
 
 var checkPropTypes_1 = checkPropTypes;
 
 /**
  * ReactElementValidator provides a wrapper around a element factory
  * which validates the props passed to the element. This is intended to be
  * used only in DEV and could be replaced by a static type checker for languages
  * that support it.
  */
 
+var propTypesMisspellWarningShown = void 0;
+
 {
-  var currentlyValidatingElement = null;
-
-  var propTypesMisspellWarningShown = false;
-
-  var getDisplayName = function (element) {
-    if (element == null) {
-      return '#empty';
-    } else if (typeof element === 'string' || typeof element === 'number') {
-      return '#text';
-    } else if (typeof element.type === 'string') {
-      return element.type;
-    } else if (element.type === REACT_FRAGMENT_TYPE) {
-      return 'React.Fragment';
-    } else {
-      return element.type.displayName || element.type.name || 'Unknown';
-    }
-  };
-
-  var getStackAddendum = function () {
-    var stack = '';
-    if (currentlyValidatingElement) {
-      var name = getDisplayName(currentlyValidatingElement);
-      var owner = currentlyValidatingElement._owner;
-      stack += describeComponentFrame(name, currentlyValidatingElement._source, owner && getComponentName(owner));
-    }
-    stack += ReactDebugCurrentFrame.getStackAddendum() || '';
-    return stack;
-  };
-
-  var VALID_FRAGMENT_PROPS = new Map([['children', true], ['key', true]]);
+  propTypesMisspellWarningShown = false;
 }
 
 function getDeclarationErrorAddendum() {
   if (ReactCurrentOwner.current) {
-    var name = getComponentName(ReactCurrentOwner.current);
+    var name = getComponentName(ReactCurrentOwner.current.type);
     if (name) {
       return '\n\nCheck the render method of `' + name + '`.';
     }
   }
   return '';
 }
 
 function getSourceInfoErrorAddendum(elementProps) {
@@ -1422,24 +2873,24 @@ function validateExplicitKey(element, pa
   ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
 
   // Usually the current owner is the offender, but if it accepts children as a
   // property, it may be the creator of the child that's responsible for
   // assigning it a key.
   var childOwner = '';
   if (element && element._owner && element._owner !== ReactCurrentOwner.current) {
     // Give the component that originally created this child.
-    childOwner = ' It was passed a child from ' + getComponentName(element._owner) + '.';
+    childOwner = ' It was passed a child from ' + getComponentName(element._owner.type) + '.';
   }
 
-  currentlyValidatingElement = element;
+  setCurrentlyValidatingElement(element);
   {
-    warning_1(false, 'Each child in an array or iterator should have a unique "key" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, getStackAddendum());
+    warning$1(false, 'Each child in a list should have a unique "key" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.', currentComponentErrorInfo, childOwner);
   }
-  currentlyValidatingElement = null;
+  setCurrentlyValidatingElement(null);
 }
 
 /**
  * Ensure that every element either is passed in a static location, in an
  * array with an explicit keys property defined, or in an object literal
  * with valid key property.
  *
  * @internal
@@ -1464,17 +2915,17 @@ function validateChildKeys(node, parentT
     }
   } else if (node) {
     var iteratorFn = getIteratorFn(node);
     if (typeof iteratorFn === 'function') {
       // Entry iterators used to provide implicit keys,
       // but now we print a separate warning for them later.
       if (iteratorFn !== node.entries) {
         var iterator = iteratorFn.call(node);
-        var step;
+        var step = void 0;
         while (!(step = iterator.next()).done) {
           if (isValidElement(step.value)) {
             validateExplicitKey(step.value, parentType);
           }
         }
       }
     }
   }
@@ -1482,97 +2933,99 @@ function validateChildKeys(node, parentT
 
 /**
  * Given an element, validate that its props follow the propTypes definition,
  * provided by the type.
  *
  * @param {ReactElement} element
  */
 function validatePropTypes(element) {
-  var componentClass = element.type;
-  if (typeof componentClass !== 'function') {
+  var type = element.type;
+  if (type === null || type === undefined || typeof type === 'string') {
     return;
   }
-  var name = componentClass.displayName || componentClass.name;
-  var propTypes = componentClass.propTypes;
+  var name = getComponentName(type);
+  var propTypes = void 0;
+  if (typeof type === 'function') {
+    propTypes = type.propTypes;
+  } else if (typeof type === 'object' && (type.$$typeof === REACT_FORWARD_REF_TYPE ||
+  // Note: Memo only checks outer props here.
+  // Inner props are checked in the reconciler.
+  type.$$typeof === REACT_MEMO_TYPE)) {
+    propTypes = type.propTypes;
+  } else {
+    return;
+  }
   if (propTypes) {
-    currentlyValidatingElement = element;
-    checkPropTypes_1(propTypes, element.props, 'prop', name, getStackAddendum);
-    currentlyValidatingElement = null;
-  } else if (componentClass.PropTypes !== undefined && !propTypesMisspellWarningShown) {
+    setCurrentlyValidatingElement(element);
+    checkPropTypes_1(propTypes, element.props, 'prop', name, ReactDebugCurrentFrame.getStackAddendum);
+    setCurrentlyValidatingElement(null);
+  } else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
     propTypesMisspellWarningShown = true;
-    warning_1(false, 'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', name || 'Unknown');
+    warningWithoutStack$1(false, 'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', name || 'Unknown');
   }
-  if (typeof componentClass.getDefaultProps === 'function') {
-    warning_1(componentClass.getDefaultProps.isReactClassApproved, 'getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.');
+  if (typeof type.getDefaultProps === 'function') {
+    !type.getDefaultProps.isReactClassApproved ? warningWithoutStack$1(false, 'getDefaultProps is only used on classic React.createClass ' + 'definitions. Use a static property named `defaultProps` instead.') : void 0;
   }
 }
 
 /**
  * Given a fragment, validate that it can only be provided with fragment props
  * @param {ReactElement} fragment
  */
 function validateFragmentProps(fragment) {
-  currentlyValidatingElement = fragment;
-
-  var _iteratorNormalCompletion = true;
-  var _didIteratorError = false;
-  var _iteratorError = undefined;
-
-  try {
-    for (var _iterator = Object.keys(fragment.props)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
-      var key = _step.value;
-
-      if (!VALID_FRAGMENT_PROPS.has(key)) {
-        warning_1(false, 'Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.%s', key, getStackAddendum());
-        break;
-      }
-    }
-  } catch (err) {
-    _didIteratorError = true;
-    _iteratorError = err;
-  } finally {
-    try {
-      if (!_iteratorNormalCompletion && _iterator['return']) {
-        _iterator['return']();
-      }
-    } finally {
-      if (_didIteratorError) {
-        throw _iteratorError;
-      }
+  setCurrentlyValidatingElement(fragment);
+
+  var keys = Object.keys(fragment.props);
+  for (var i = 0; i < keys.length; i++) {
+    var key = keys[i];
+    if (key !== 'children' && key !== 'key') {
+      warning$1(false, 'Invalid prop `%s` supplied to `React.Fragment`. ' + 'React.Fragment can only have `key` and `children` props.', key);
+      break;
     }
   }
 
   if (fragment.ref !== null) {
-    warning_1(false, 'Invalid attribute `ref` supplied to `React.Fragment`.%s', getStackAddendum());
+    warning$1(false, 'Invalid attribute `ref` supplied to `React.Fragment`.');
   }
 
-  currentlyValidatingElement = null;
+  setCurrentlyValidatingElement(null);
 }
 
 function createElementWithValidation(type, props, children) {
-  var validType = typeof type === 'string' || typeof type === 'function' || typeof type === 'symbol' || typeof type === 'number';
+  var validType = isValidElementType(type);
+
   // We warn in this case but don't throw. We expect the element creation to
   // succeed and there will likely be errors in render.
   if (!validType) {
     var info = '';
     if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
       info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and named imports.";
     }
 
     var sourceInfo = getSourceInfoErrorAddendum(props);
     if (sourceInfo) {
       info += sourceInfo;
     } else {
       info += getDeclarationErrorAddendum();
     }
 
-    info += getStackAddendum() || '';
-
-    warning_1(false, 'React.createElement: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', type == null ? type : typeof type, info);
+    var typeString = void 0;
+    if (type === null) {
+      typeString = 'null';
+    } else if (Array.isArray(type)) {
+      typeString = 'array';
+    } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
+      typeString = '<' + (getComponentName(type.type) || 'Unknown') + ' />';
+      info = ' Did you accidentally export a JSX literal instead of a component?';
+    } else {
+      typeString = typeof type;
+    }
+
+    warning$1(false, 'React.createElement: type is invalid -- expected a string (for ' + 'built-in components) or a class/function (for composite ' + 'components) but got: %s.%s', typeString, info);
   }
 
   var element = createElement.apply(this, arguments);
 
   // The result can be nullish if a mock or a custom function is used.
   // TODO: Drop this when these are no longer allowed as the type argument.
   if (element == null) {
     return element;
@@ -1584,30 +3037,29 @@ function createElementWithValidation(typ
   // (Rendering will throw with a helpful message and as soon as the type is
   // fixed, the key warnings will appear.)
   if (validType) {
     for (var i = 2; i < arguments.length; i++) {
       validateChildKeys(arguments[i], type);
     }
   }
 
-  if (typeof type === 'symbol' && type === REACT_FRAGMENT_TYPE) {
+  if (type === REACT_FRAGMENT_TYPE) {
     validateFragmentProps(element);
   } else {
     validatePropTypes(element);
   }
 
   return element;
 }
 
 function createFactoryWithValidation(type) {
   var validatedFactory = createElementWithValidation.bind(null, type);
-  // Legacy hook TODO: Warn if this is accessed
   validatedFactory.type = type;
-
+  // Legacy hook: remove it
   {
     Object.defineProperty(validatedFactory, 'type', {
       enumerable: false,
       get: function () {
         lowPriorityWarning$1(false, 'Factory.type is deprecated. Access the class directly ' + 'before passing it to createFactory.');
         Object.defineProperty(this, 'type', {
           value: type
         });
@@ -1632,53 +3084,72 @@ var React = {
   Children: {
     map: mapChildren,
     forEach: forEachChildren,
     count: countChildren,
     toArray: toArray,
     only: onlyChild
   },
 
+  createRef: createRef,
   Component: Component,
   PureComponent: PureComponent,
-  unstable_AsyncComponent: AsyncComponent,
+
+  createContext: createContext,
+  forwardRef: forwardRef,
+  lazy: lazy,
+  memo: memo,
+
+  useCallback: useCallback,
+  useContext: useContext,
+  useEffect: useEffect,
+  useImperativeHandle: useImperativeHandle,
+  useDebugValue: useDebugValue,
+  useLayoutEffect: useLayoutEffect,
+  useMemo: useMemo,
+  useReducer: useReducer,
+  useRef: useRef,
+  useState: useState,
 
   Fragment: REACT_FRAGMENT_TYPE,
+  StrictMode: REACT_STRICT_MODE_TYPE,
+  Suspense: REACT_SUSPENSE_TYPE,
 
   createElement: createElementWithValidation,
   cloneElement: cloneElementWithValidation,
   createFactory: createFactoryWithValidation,
   isValidElement: isValidElement,
 
   version: ReactVersion,
 
-  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
-    ReactCurrentOwner: ReactCurrentOwner,
-    // Used by renderers to avoid bundling object-assign twice in UMD bundles:
-    assign: objectAssign
-  }
+  unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
+  unstable_Profiler: REACT_PROFILER_TYPE,
+
+  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals
 };
 
-{
-  objectAssign(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, {
-    // These should not be included in production.
-    ReactDebugCurrentFrame: ReactDebugCurrentFrame,
-    // Shim for React DOM 16.0.0 which still destructured (but not used) this.
-    // TODO: remove in React 17.0.
-    ReactComponentTreeHook: {}
-  });
+// Note: some APIs are added with feature flags.
+// Make sure that stable builds for open source
+// don't modify the React object to avoid deopts.
+// Also let's not expose their names in stable builds.
+
+if (enableStableConcurrentModeAPIs) {
+  React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
+  React.Profiler = REACT_PROFILER_TYPE;
+  React.unstable_ConcurrentMode = undefined;
+  React.unstable_Profiler = undefined;
 }
 
 
 
 var React$2 = Object.freeze({
 	default: React
 });
 
 var React$3 = ( React$2 && React ) || React$2;
 
 // TODO: decide on the top-level export form.
 // This is hacky but makes it work with both Rollup and Jest.
-var react = React$3['default'] ? React$3['default'] : React$3;
+var react = React$3.default || React$3;
 
 return react;
 
 })));
--- a/browser/components/newtab/vendor/react-dom-dev.js
+++ b/browser/components/newtab/vendor/react-dom-dev.js
@@ -1,552 +1,90 @@
-/** @license React v16.2.0
+/** @license React v16.8.3
  * react-dom.development.js
  *
- * Copyright (c) 2013-present, Facebook, Inc.
+ * Copyright (c) Facebook, Inc. and its affiliates.
  *
  * This source code is licensed under the MIT license found in the
  * LICENSE file in the root directory of this source tree.
  */
 
 'use strict';
 
 (function (global, factory) {
 	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) :
 	typeof define === 'function' && define.amd ? define(['react'], factory) :
 	(global.ReactDOM = factory(global.React));
 }(this, (function (React) { 'use strict';
 
 /**
- * WARNING: DO NOT manually require this module.
- * This is a replacement for `invariant(...)` used by the error code system
- * and will _only_ be required by the corresponding babel pass.
- * It always throws.
- */
-
-/**
- * Copyright (c) 2013-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-
-
-/**
  * Use invariant() to assert state which your program assumes to be true.
  *
  * Provide sprintf-style format (only %s is supported) and arguments
  * to provide information about what broke and what you were
  * expecting.
  *
  * The invariant message will be stripped in production, but the invariant
  * will remain to ensure logic does not differ in production.
  */
 
-var validateFormat = function validateFormat(format) {};
+var validateFormat = function () {};
 
 {
-  validateFormat = function validateFormat(format) {
+  validateFormat = function (format) {
     if (format === undefined) {
       throw new Error('invariant requires an error message argument');
     }
   };
 }
 
 function invariant(condition, format, a, b, c, d, e, f) {
   validateFormat(format);
 
   if (!condition) {
-    var error;
+    var error = void 0;
     if (format === undefined) {
       error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.');
     } else {
       var args = [a, b, c, d, e, f];
       var argIndex = 0;
       error = new Error(format.replace(/%s/g, function () {
         return args[argIndex++];
       }));
       error.name = 'Invariant Violation';
     }
 
     error.framesToPop = 1; // we don't care about invariant's own frame
     throw error;
   }
 }
 
-var invariant_1 = invariant;
-
-!React ? invariant_1(false, 'ReactDOM was loaded before React. Make sure you load the React package before loading ReactDOM.') : void 0;
-
-// These attributes should be all lowercase to allow for
-// case insensitive checks
-var RESERVED_PROPS = {
-  children: true,
-  dangerouslySetInnerHTML: true,
-  defaultValue: true,
-  defaultChecked: true,
-  innerHTML: true,
-  suppressContentEditableWarning: true,
-  suppressHydrationWarning: true,
-  style: true
-};
-
-function checkMask(value, bitmask) {
-  return (value & bitmask) === bitmask;
-}
-
-var DOMPropertyInjection = {
-  /**
-   * Mapping from normalized, camelcased property names to a configuration that
-   * specifies how the associated DOM property should be accessed or rendered.
-   */
-  MUST_USE_PROPERTY: 0x1,
-  HAS_BOOLEAN_VALUE: 0x4,
-  HAS_NUMERIC_VALUE: 0x8,
-  HAS_POSITIVE_NUMERIC_VALUE: 0x10 | 0x8,
-  HAS_OVERLOADED_BOOLEAN_VALUE: 0x20,
-  HAS_STRING_BOOLEAN_VALUE: 0x40,
-
-  /**
-   * Inject some specialized knowledge about the DOM. This takes a config object
-   * with the following properties:
-   *
-   * Properties: object mapping DOM property name to one of the
-   * DOMPropertyInjection constants or null. If your attribute isn't in here,
-   * it won't get written to the DOM.
-   *
-   * DOMAttributeNames: object mapping React attribute name to the DOM
-   * attribute name. Attribute names not specified use the **lowercase**
-   * normalized name.
-   *
-   * DOMAttributeNamespaces: object mapping React attribute name to the DOM
-   * attribute namespace URL. (Attribute names not specified use no namespace.)
-   *
-   * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties.
-   * Property names not specified use the normalized name.
-   *
-   * DOMMutationMethods: Properties that require special mutation methods. If
-   * `value` is undefined, the mutation method should unset the property.
-   *
-   * @param {object} domPropertyConfig the config as described above.
-   */
-  injectDOMPropertyConfig: function (domPropertyConfig) {
-    var Injection = DOMPropertyInjection;
-    var Properties = domPropertyConfig.Properties || {};
-    var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {};
-    var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
-    var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {};
-
-    for (var propName in Properties) {
-      !!properties.hasOwnProperty(propName) ? invariant_1(false, "injectDOMPropertyConfig(...): You're trying to inject DOM property '%s' which has already been injected. You may be accidentally injecting the same DOM property config twice, or you may be injecting two configs that have conflicting property names.", propName) : void 0;
-
-      var lowerCased = propName.toLowerCase();
-      var propConfig = Properties[propName];
-
-      var propertyInfo = {
-        attributeName: lowerCased,
-        attributeNamespace: null,
-        propertyName: propName,
-        mutationMethod: null,
-
-        mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY),
-        hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE),
-        hasNumericValue: checkMask(propConfig, Injection.HAS_NUMERIC_VALUE),
-        hasPositiveNumericValue: checkMask(propConfig, Injection.HAS_POSITIVE_NUMERIC_VALUE),
-        hasOverloadedBooleanValue: checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE),
-        hasStringBooleanValue: checkMask(propConfig, Injection.HAS_STRING_BOOLEAN_VALUE)
-      };
-      !(propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue + propertyInfo.hasOverloadedBooleanValue <= 1) ? invariant_1(false, "DOMProperty: Value can be one of boolean, overloaded boolean, or numeric value, but not a combination: %s", propName) : void 0;
-
-      if (DOMAttributeNames.hasOwnProperty(propName)) {
-        var attributeName = DOMAttributeNames[propName];
-
-        propertyInfo.attributeName = attributeName;
-      }
-
-      if (DOMAttributeNamespaces.hasOwnProperty(propName)) {
-        propertyInfo.attributeNamespace = DOMAttributeNamespaces[propName];
-      }
-
-      if (DOMMutationMethods.hasOwnProperty(propName)) {
-        propertyInfo.mutationMethod = DOMMutationMethods[propName];
-      }
-
-      // Downcase references to whitelist properties to check for membership
-      // without case-sensitivity. This allows the whitelist to pick up
-      // `allowfullscreen`, which should be written using the property configuration
-      // for `allowFullscreen`
-      properties[propName] = propertyInfo;
-    }
-  }
-};
-
-/* eslint-disable max-len */
-var ATTRIBUTE_NAME_START_CHAR = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
-/* eslint-enable max-len */
-var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + "\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
-
-
-var ROOT_ATTRIBUTE_NAME = 'data-reactroot';
-
-/**
- * Map from property "standard name" to an object with info about how to set
- * the property in the DOM. Each object contains:
- *
- * attributeName:
- *   Used when rendering markup or with `*Attribute()`.
- * attributeNamespace
- * propertyName:
- *   Used on DOM node instances. (This includes properties that mutate due to
- *   external factors.)
- * mutationMethod:
- *   If non-null, used instead of the property or `setAttribute()` after
- *   initial render.
- * mustUseProperty:
- *   Whether the property must be accessed and mutated as an object property.
- * hasBooleanValue:
- *   Whether the property should be removed when set to a falsey value.
- * hasNumericValue:
- *   Whether the property must be numeric or parse as a numeric and should be
- *   removed when set to a falsey value.
- * hasPositiveNumericValue:
- *   Whether the property must be positive numeric or parse as a positive
- *   numeric and should be removed when set to a falsey value.
- * hasOverloadedBooleanValue:
- *   Whether the property can be used as a flag as well as with a value.
- *   Removed when strictly equal to false; present without a value when
- *   strictly equal to true; present with a value otherwise.
- */
-var properties = {};
-
-/**
- * Checks whether a property name is a writeable attribute.
- * @method
- */
-function shouldSetAttribute(name, value) {
-  if (isReservedProp(name)) {
-    return false;
-  }
-  if (name.length > 2 && (name[0] === 'o' || name[0] === 'O') && (name[1] === 'n' || name[1] === 'N')) {
-    return false;
-  }
-  if (value === null) {
-    return true;
-  }
-  switch (typeof value) {
-    case 'boolean':
-      return shouldAttributeAcceptBooleanValue(name);
-    case 'undefined':
-    case 'number':
-    case 'string':
-    case 'object':
-      return true;
-    default:
-      // function, symbol
-      return false;
-  }
-}
-
-function getPropertyInfo(name) {
-  return properties.hasOwnProperty(name) ? properties[name] : null;
-}
-
-function shouldAttributeAcceptBooleanValue(name) {
-  if (isReservedProp(name)) {
-    return true;
-  }
-  var propertyInfo = getPropertyInfo(name);
-  if (propertyInfo) {
-    return propertyInfo.hasBooleanValue || propertyInfo.hasStringBooleanValue || propertyInfo.hasOverloadedBooleanValue;
-  }
-  var prefix = name.toLowerCase().slice(0, 5);
-  return prefix === 'data-' || prefix === 'aria-';
-}
-
-/**
- * Checks to see if a property name is within the list of properties
- * reserved for internal React operations. These properties should
- * not be set on an HTML element.
- *
- * @private
- * @param {string} name
- * @return {boolean} If the name is within reserved props
- */
-function isReservedProp(name) {
-  return RESERVED_PROPS.hasOwnProperty(name);
-}
-
-var injection = DOMPropertyInjection;
-
-var MUST_USE_PROPERTY = injection.MUST_USE_PROPERTY;
-var HAS_BOOLEAN_VALUE = injection.HAS_BOOLEAN_VALUE;
-var HAS_NUMERIC_VALUE = injection.HAS_NUMERIC_VALUE;
-var HAS_POSITIVE_NUMERIC_VALUE = injection.HAS_POSITIVE_NUMERIC_VALUE;
-var HAS_OVERLOADED_BOOLEAN_VALUE = injection.HAS_OVERLOADED_BOOLEAN_VALUE;
-var HAS_STRING_BOOLEAN_VALUE = injection.HAS_STRING_BOOLEAN_VALUE;
-
-var HTMLDOMPropertyConfig = {
-  // When adding attributes to this list, be sure to also add them to
-  // the `possibleStandardNames` module to ensure casing and incorrect
-  // name warnings.
-  Properties: {
-    allowFullScreen: HAS_BOOLEAN_VALUE,
-    // specifies target context for links with `preload` type
-    async: HAS_BOOLEAN_VALUE,
-    // Note: there is a special case that prevents it from being written to the DOM
-    // on the client side because the browsers are inconsistent. Instead we call focus().
-    autoFocus: HAS_BOOLEAN_VALUE,
-    autoPlay: HAS_BOOLEAN_VALUE,
-    capture: HAS_OVERLOADED_BOOLEAN_VALUE,
-    checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
-    cols: HAS_POSITIVE_NUMERIC_VALUE,
-    contentEditable: HAS_STRING_BOOLEAN_VALUE,
-    controls: HAS_BOOLEAN_VALUE,
-    'default': HAS_BOOLEAN_VALUE,
-    defer: HAS_BOOLEAN_VALUE,
-    disabled: HAS_BOOLEAN_VALUE,
-    download: HAS_OVERLOADED_BOOLEAN_VALUE,
-    draggable: HAS_STRING_BOOLEAN_VALUE,
-    formNoValidate: HAS_BOOLEAN_VALUE,
-    hidden: HAS_BOOLEAN_VALUE,
-    loop: HAS_BOOLEAN_VALUE,
-    // Caution; `option.selected` is not updated if `select.multiple` is
-    // disabled with `removeAttribute`.
-    multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
-    muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
-    noValidate: HAS_BOOLEAN_VALUE,
-    open: HAS_BOOLEAN_VALUE,
-    playsInline: HAS_BOOLEAN_VALUE,
-    readOnly: HAS_BOOLEAN_VALUE,
-    required: HAS_BOOLEAN_VALUE,
-    reversed: HAS_BOOLEAN_VALUE,
-    rows: HAS_POSITIVE_NUMERIC_VALUE,
-    rowSpan: HAS_NUMERIC_VALUE,
-    scoped: HAS_BOOLEAN_VALUE,
-    seamless: HAS_BOOLEAN_VALUE,
-    selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
-    size: HAS_POSITIVE_NUMERIC_VALUE,
-    start: HAS_NUMERIC_VALUE,
-    // support for projecting regular DOM Elements via V1 named slots ( shadow dom )
-    span: HAS_POSITIVE_NUMERIC_VALUE,
-    spellCheck: HAS_STRING_BOOLEAN_VALUE,
-    // Style must be explicitly set in the attribute list. React components
-    // expect a style object
-    style: 0,
-    // Keep it in the whitelist because it is case-sensitive for SVG.
-    tabIndex: 0,
-    // itemScope is for for Microdata support.
-    // See http://schema.org/docs/gs.html
-    itemScope: HAS_BOOLEAN_VALUE,
-    // These attributes must stay in the white-list because they have
-    // different attribute names (see DOMAttributeNames below)
-    acceptCharset: 0,
-    className: 0,
-    htmlFor: 0,
-    httpEquiv: 0,
-    // Attributes with mutation methods must be specified in the whitelist
-    // Set the string boolean flag to allow the behavior
-    value: HAS_STRING_BOOLEAN_VALUE
-  },
-  DOMAttributeNames: {
-    acceptCharset: 'accept-charset',
-    className: 'class',
-    htmlFor: 'for',
-    httpEquiv: 'http-equiv'
-  },
-  DOMMutationMethods: {
-    value: function (node, value) {
-      if (value == null) {
-        return node.removeAttribute('value');
-      }
-
-      // Number inputs get special treatment due to some edge cases in
-      // Chrome. Let everything else assign the value attribute as normal.
-      // https://github.com/facebook/react/issues/7253#issuecomment-236074326
-      if (node.type !== 'number' || node.hasAttribute('value') === false) {
-        node.setAttribute('value', '' + value);
-      } else if (node.validity && !node.validity.badInput && node.ownerDocument.activeElement !== node) {
-        // Don't assign an attribute if validation reports bad
-        // input. Chrome will clear the value. Additionally, don't
-        // operate on inputs that have focus, otherwise Chrome might
-        // strip off trailing decimal places and cause the user's
-        // cursor position to jump to the beginning of the input.
-        //
-        // In ReactDOMInput, we have an onBlur event that will trigger
-        // this function again when focus is lost.
-        node.setAttribute('value', '' + value);
-      }
-    }
-  }
-};
-
-var HAS_STRING_BOOLEAN_VALUE$1 = injection.HAS_STRING_BOOLEAN_VALUE;
-
-
-var NS = {
-  xlink: 'http://www.w3.org/1999/xlink',
-  xml: 'http://www.w3.org/XML/1998/namespace'
-};
-
-/**
- * This is a list of all SVG attributes that need special casing,
- * namespacing, or boolean value assignment.
- *
- * When adding attributes to this list, be sure to also add them to
- * the `possibleStandardNames` module to ensure casing and incorrect
- * name warnings.
- *
- * SVG Attributes List:
- * https://www.w3.org/TR/SVG/attindex.html
- * SMIL Spec:
- * https://www.w3.org/TR/smil
- */
-var ATTRS = ['accent-height', 'alignment-baseline', 'arabic-form', 'baseline-shift', 'cap-height', 'clip-path', 'clip-rule', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'dominant-baseline', 'enable-background', 'fill-opacity', 'fill-rule', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-name', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'horiz-adv-x', 'horiz-origin-x', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'overline-position', 'overline-thickness', 'paint-order', 'panose-1', 'pointer-events', 'rendering-intent', 'shape-rendering', 'stop-color', 'stop-opacity', 'strikethrough-position', 'strikethrough-thickness', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'underline-position', 'underline-thickness', 'unicode-bidi', 'unicode-range', 'units-per-em', 'v-alphabetic', 'v-hanging', 'v-ideographic', 'v-mathematical', 'vector-effect', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'word-spacing', 'writing-mode', 'x-height', 'xlink:actuate', 'xlink:arcrole', 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type', 'xml:base', 'xmlns:xlink', 'xml:lang', 'xml:space'];
-
-var SVGDOMPropertyConfig = {
-  Properties: {
-    autoReverse: HAS_STRING_BOOLEAN_VALUE$1,
-    externalResourcesRequired: HAS_STRING_BOOLEAN_VALUE$1,
-    preserveAlpha: HAS_STRING_BOOLEAN_VALUE$1
-  },
-  DOMAttributeNames: {
-    autoReverse: 'autoReverse',
-    externalResourcesRequired: 'externalResourcesRequired',
-    preserveAlpha: 'preserveAlpha'
-  },
-  DOMAttributeNamespaces: {
-    xlinkActuate: NS.xlink,
-    xlinkArcrole: NS.xlink,
-    xlinkHref: NS.xlink,
-    xlinkRole: NS.xlink,
-    xlinkShow: NS.xlink,
-    xlinkTitle: NS.xlink,
-    xlinkType: NS.xlink,
-    xmlBase: NS.xml,
-    xmlLang: NS.xml,
-    xmlSpace: NS.xml
-  }
-};
-
-var CAMELIZE = /[\-\:]([a-z])/g;
-var capitalize = function (token) {
-  return token[1].toUpperCase();
-};
-
-ATTRS.forEach(function (original) {
-  var reactName = original.replace(CAMELIZE, capitalize);
-
-  SVGDOMPropertyConfig.Properties[reactName] = 0;
-  SVGDOMPropertyConfig.DOMAttributeNames[reactName] = original;
-});
-
-injection.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
-injection.injectDOMPropertyConfig(SVGDOMPropertyConfig);
-
-var ReactErrorUtils = {
-  // Used by Fiber to simulate a try-catch.
-  _caughtError: null,
-  _hasCaughtError: false,
-
-  // Used by event system to capture/rethrow the first error.
-  _rethrowError: null,
-  _hasRethrowError: false,
-
-  injection: {
-    injectErrorUtils: function (injectedErrorUtils) {
-      !(typeof injectedErrorUtils.invokeGuardedCallback === 'function') ? invariant_1(false, 'Injected invokeGuardedCallback() must be a function.') : void 0;
-      invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback;
-    }
-  },
-
-  /**
-   * Call a function while guarding against errors that happens within it.
-   * Returns an error if it throws, otherwise null.
-   *
-   * In production, this is implemented using a try-catch. The reason we don't
-   * use a try-catch directly is so that we can swap out a different
-   * implementation in DEV mode.
-   *
-   * @param {String} name of the guard to use for logging or debugging
-   * @param {Function} func The function to invoke
-   * @param {*} context The context to use when calling the function
-   * @param {...*} args Arguments for function
-   */
-  invokeGuardedCallback: function (name, func, context, a, b, c, d, e, f) {
-    invokeGuardedCallback.apply(ReactErrorUtils, arguments);
-  },
-
-  /**
-   * Same as invokeGuardedCallback, but instead of returning an error, it stores
-   * it in a global so it can be rethrown by `rethrowCaughtError` later.
-   * TODO: See if _caughtError and _rethrowError can be unified.
-   *
-   * @param {String} name of the guard to use for logging or debugging
-   * @param {Function} func The function to invoke
-   * @param {*} context The context to use when calling the function
-   * @param {...*} args Arguments for function
-   */
-  invokeGuardedCallbackAndCatchFirstError: function (name, func, context, a, b, c, d, e, f) {
-    ReactErrorUtils.invokeGuardedCallback.apply(this, arguments);
-    if (ReactErrorUtils.hasCaughtError()) {
-      var error = ReactErrorUtils.clearCaughtError();
-      if (!ReactErrorUtils._hasRethrowError) {
-        ReactErrorUtils._hasRethrowError = true;
-        ReactErrorUtils._rethrowError = error;
-      }
-    }
-  },
-
-  /**
-   * During execution of guarded functions we will capture the first error which
-   * we will rethrow to be handled by the top level error handler.
-   */
-  rethrowCaughtError: function () {
-    return rethrowCaughtError.apply(ReactErrorUtils, arguments);
-  },
-
-  hasCaughtError: function () {
-    return ReactErrorUtils._hasCaughtError;
-  },
-
-  clearCaughtError: function () {
-    if (ReactErrorUtils._hasCaughtError) {
-      var error = ReactErrorUtils._caughtError;
-      ReactErrorUtils._caughtError = null;
-      ReactErrorUtils._hasCaughtError = false;
-      return error;
-    } else {
-      invariant_1(false, 'clearCaughtError was called but no error was captured. This error is likely caused by a bug in React. Please file an issue.');
-    }
-  }
-};
-
-var invokeGuardedCallback = function (name, func, context, a, b, c, d, e, f) {
-  ReactErrorUtils._hasCaughtError = false;
-  ReactErrorUtils._caughtError = null;
+// Relying on the `invariant()` implementation lets us
+// preserve the format and params in the www builds.
+
+!React ? invariant(false, 'ReactDOM was loaded before React. Make sure you load the React package before loading ReactDOM.') : void 0;
+
+var invokeGuardedCallbackImpl = function (name, func, context, a, b, c, d, e, f) {
   var funcArgs = Array.prototype.slice.call(arguments, 3);
   try {
     func.apply(context, funcArgs);
   } catch (error) {
-    ReactErrorUtils._caughtError = error;
-    ReactErrorUtils._hasCaughtError = true;
+    this.onError(error);
   }
 };
 
 {
   // In DEV mode, we swap out invokeGuardedCallback for a special version
   // that plays more nicely with the browser's DevTools. The idea is to preserve
   // "Pause on exceptions" behavior. Because React wraps all user-provided
   // functions in invokeGuardedCallback, and the production version of
   // invokeGuardedCallback uses a try-catch, all user exceptions are treated
   // like caught exceptions, and the DevTools won't pause unless the developer
   // takes the extra step of enabling pause on caught exceptions. This is
-  // untintuitive, though, because even though React has caught the error, from
+  // unintuitive, though, because even though React has caught the error, from
   // the developer's perspective, the error is uncaught.
   //
   // To preserve the expected "Pause on exceptions" behavior, we don't use a
   // try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
   // DOM node, and call the user-provided callback from inside an event handler
   // for that fake event. If the callback throws, the error is "captured" using
   // a global event handler. But because the error happens in a different
   // event loop context, it does not interrupt the normal program flow.
@@ -554,34 +92,59 @@ var invokeGuardedCallback = function (na
   // try-catch. Neat!
 
   // Check that the browser supports the APIs we need to implement our special
   // DEV version of invokeGuardedCallback
   if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document !== 'undefined' && typeof document.createEvent === 'function') {
     var fakeNode = document.createElement('react');
 
     var invokeGuardedCallbackDev = function (name, func, context, a, b, c, d, e, f) {
+      // If document doesn't exist we know for sure we will crash in this method
+      // when we call document.createEvent(). However this can cause confusing
+      // errors: https://github.com/facebookincubator/create-react-app/issues/3482
+      // So we preemptively throw with a better message instead.
+      !(typeof document !== 'undefined') ? invariant(false, 'The `document` global was defined when React was initialized, but is not defined anymore. This can happen in a test environment if a component schedules an update from an asynchronous callback, but the test has already finished running. To solve this, you can either unmount the component at the end of your test (and ensure that any asynchronous operations get canceled in `componentWillUnmount`), or you can change the test itself to be asynchronous.') : void 0;
+      var evt = document.createEvent('Event');
+
       // Keeps track of whether the user-provided callback threw an error. We
       // set this to true at the beginning, then set it to false right after
       // calling the function. If the function errors, `didError` will never be
       // set to false. This strategy works even if the browser is flaky and
       // fails to call our global error handler, because it doesn't rely on
       // the error event at all.
       var didError = true;
 
+      // Keeps track of the value of window.event so that we can reset it
+      // during the callback to let user code access window.event in the
+      // browsers that support it.
+      var windowEvent = window.event;
+
+      // Keeps track of the descriptor of window.event to restore it after event
+      // dispatching: https://github.com/facebook/react/issues/13688
+      var windowEventDescriptor = Object.getOwnPropertyDescriptor(window, 'event');
+
       // Create an event handler for our fake event. We will synchronously
       // dispatch our fake event using `dispatchEvent`. Inside the handler, we
       // call the user-provided callback.
       var funcArgs = Array.prototype.slice.call(arguments, 3);
       function callCallback() {
         // We immediately remove the callback from event listeners so that
         // nested `invokeGuardedCallback` calls do not clash. Otherwise, a
         // nested call would trigger the fake event handlers of any call higher
         // in the stack.
         fakeNode.removeEventListener(evtType, callCallback, false);
+
+        // We check for window.hasOwnProperty('event') to prevent the
+        // window.event assignment in both IE <= 10 as they throw an error
+        // "Member not found" in strict mode, and in Firefox which does not
+        // support window.event.
+        if (typeof window.event !== 'undefined' && window.hasOwnProperty('event')) {
+          window.event = windowEvent;
+        }
+
         func.apply(context, funcArgs);
         didError = false;
       }
 
       // Create a global error event handler. We use this to capture the value
       // that was thrown. It's possible that this error handler will fire more
       // than once; for example, if non-React code also calls `dispatchEvent`
       // and a handler for that event throws. We should be resilient to most of
@@ -592,67 +155,154 @@ var invokeGuardedCallback = function (na
       // erroring and the code that follows the `dispatchEvent` call below. If
       // the callback doesn't error, but the error event was fired, we know to
       // ignore it because `didError` will be false, as described above.
       var error = void 0;
       // Use this to track whether the error event is ever called.
       var didSetError = false;
       var isCrossOriginError = false;
 
-      function onError(event) {
+      function handleWindowError(event) {
         error = event.error;
         didSetError = true;
         if (error === null && event.colno === 0 && event.lineno === 0) {
           isCrossOriginError = true;
         }
+        if (event.defaultPrevented) {
+          // Some other error handler has prevented default.
+          // Browsers silence the error report if this happens.
+          // We'll remember this to later decide whether to log it or not.
+          if (error != null && typeof error === 'object') {
+            try {
+              error._suppressLogging = true;
+            } catch (inner) {
+              // Ignore.
+            }
+          }
+        }
       }
 
       // Create a fake event type.
       var evtType = 'react-' + (name ? name : 'invokeguardedcallback');
 
       // Attach our event handlers
-      window.addEventListener('error', onError);
+      window.addEventListener('error', handleWindowError);
       fakeNode.addEventListener(evtType, callCallback, false);
 
       // Synchronously dispatch our fake event. If the user-provided function
       // errors, it will trigger our global error handler.
-      var evt = document.createEvent('Event');
       evt.initEvent(evtType, false, false);
       fakeNode.dispatchEvent(evt);
 
+      if (windowEventDescriptor) {
+        Object.defineProperty(window, 'event', windowEventDescriptor);
+      }
+
       if (didError) {
         if (!didSetError) {
           // The callback errored, but the error event never fired.
           error = new Error('An error was thrown inside one of your components, but React ' + "doesn't know what it was. This is likely due to browser " + 'flakiness. React does its best to preserve the "Pause on ' + 'exceptions" behavior of the DevTools, which requires some ' + "DEV-mode only tricks. It's possible that these don't work in " + 'your browser. Try triggering the error in production mode, ' + 'or switching to a modern browser. If you suspect that this is ' + 'actually an issue with React, please file an issue.');
         } else if (isCrossOriginError) {
           error = new Error("A cross-origin error was thrown. React doesn't have access to " + 'the actual error object in development. ' + 'See https://fb.me/react-crossorigin-error for more information.');
         }
-        ReactErrorUtils._hasCaughtError = true;
-        ReactErrorUtils._caughtError = error;
-      } else {
-        ReactErrorUtils._hasCaughtError = false;
-        ReactErrorUtils._caughtError = null;
+        this.onError(error);
       }
 
       // Remove our event listeners
-      window.removeEventListener('error', onError);
+      window.removeEventListener('error', handleWindowError);
     };
 
-    invokeGuardedCallback = invokeGuardedCallbackDev;
-  }
-}
-
-var rethrowCaughtError = function () {
-  if (ReactErrorUtils._hasRethrowError) {
-    var error = ReactErrorUtils._rethrowError;
-    ReactErrorUtils._rethrowError = null;
-    ReactErrorUtils._hasRethrowError = false;
+    invokeGuardedCallbackImpl = invokeGuardedCallbackDev;
+  }
+}
+
+var invokeGuardedCallbackImpl$1 = invokeGuardedCallbackImpl;
+
+// Used by Fiber to simulate a try-catch.
+var hasError = false;
+var caughtError = null;
+
+// Used by event system to capture/rethrow the first error.
+var hasRethrowError = false;
+var rethrowError = null;
+
+var reporter = {
+  onError: function (error) {
+    hasError = true;
+    caughtError = error;
+  }
+};
+
+/**
+ * Call a function while guarding against errors that happens within it.
+ * Returns an error if it throws, otherwise null.
+ *
+ * In production, this is implemented using a try-catch. The reason we don't
+ * use a try-catch directly is so that we can swap out a different
+ * implementation in DEV mode.
+ *
+ * @param {String} name of the guard to use for logging or debugging
+ * @param {Function} func The function to invoke
+ * @param {*} context The context to use when calling the function
+ * @param {...*} args Arguments for function
+ */
+function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
+  hasError = false;
+  caughtError = null;
+  invokeGuardedCallbackImpl$1.apply(reporter, arguments);
+}
+
+/**
+ * Same as invokeGuardedCallback, but instead of returning an error, it stores
+ * it in a global so it can be rethrown by `rethrowCaughtError` later.
+ * TODO: See if caughtError and rethrowError can be unified.
+ *
+ * @param {String} name of the guard to use for logging or debugging
+ * @param {Function} func The function to invoke
+ * @param {*} context The context to use when calling the function
+ * @param {...*} args Arguments for function
+ */
+function invokeGuardedCallbackAndCatchFirstError(name, func, context, a, b, c, d, e, f) {
+  invokeGuardedCallback.apply(this, arguments);
+  if (hasError) {
+    var error = clearCaughtError();
+    if (!hasRethrowError) {
+      hasRethrowError = true;
+      rethrowError = error;
+    }
+  }
+}
+
+/**
+ * During execution of guarded functions we will capture the first error which
+ * we will rethrow to be handled by the top level error handler.
+ */
+function rethrowCaughtError() {
+  if (hasRethrowError) {
+    var error = rethrowError;
+    hasRethrowError = false;
+    rethrowError = null;
     throw error;
   }
-};
+}
+
+function hasCaughtError() {
+  return hasError;
+}
+
+function clearCaughtError() {
+  if (hasError) {
+    var error = caughtError;
+    hasError = false;
+    caughtError = null;
+    return error;
+  } else {
+    invariant(false, 'clearCaughtError was called but no error was captured. This error is likely caused by a bug in React. Please file an issue.');
+  }
+}
 
 /**
  * Injectable ordering of event plugins.
  */
 var eventPluginOrder = null;
 
 /**
  * Injectable mapping from names to event plugin modules.
@@ -667,39 +317,39 @@ var namesToPlugins = {};
 function recomputePluginOrdering() {
   if (!eventPluginOrder) {
     // Wait until an `eventPluginOrder` is injected.
     return;
   }
   for (var pluginName in namesToPlugins) {
     var pluginModule = namesToPlugins[pluginName];
     var pluginIndex = eventPluginOrder.indexOf(pluginName);
-    !(pluginIndex > -1) ? invariant_1(false, 'EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `%s`.', pluginName) : void 0;
+    !(pluginIndex > -1) ? invariant(false, 'EventPluginRegistry: Cannot inject event plugins that do not exist in the plugin ordering, `%s`.', pluginName) : void 0;
     if (plugins[pluginIndex]) {
       continue;
     }
-    !pluginModule.extractEvents ? invariant_1(false, 'EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `%s` does not.', pluginName) : void 0;
+    !pluginModule.extractEvents ? invariant(false, 'EventPluginRegistry: Event plugins must implement an `extractEvents` method, but `%s` does not.', pluginName) : void 0;
     plugins[pluginIndex] = pluginModule;
     var publishedEvents = pluginModule.eventTypes;
     for (var eventName in publishedEvents) {
-      !publishEventForPlugin(publishedEvents[eventName], pluginModule, eventName) ? invariant_1(false, 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', eventName, pluginName) : void 0;
+      !publishEventForPlugin(publishedEvents[eventName], pluginModule, eventName) ? invariant(false, 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.', eventName, pluginName) : void 0;
     }
   }
 }
 
 /**
  * Publishes an event so that it can be dispatched by the supplied plugin.
  *
  * @param {object} dispatchConfig Dispatch configuration for the event.
  * @param {object} PluginModule Plugin publishing the event.
  * @return {boolean} True if the event was successfully published.
  * @private
  */
 function publishEventForPlugin(dispatchConfig, pluginModule, eventName) {
-  !!eventNameDispatchConfigs.hasOwnProperty(eventName) ? invariant_1(false, 'EventPluginHub: More than one plugin attempted to publish the same event name, `%s`.', eventName) : void 0;
+  !!eventNameDispatchConfigs.hasOwnProperty(eventName) ? invariant(false, 'EventPluginHub: More than one plugin attempted to publish the same event name, `%s`.', eventName) : void 0;
   eventNameDispatchConfigs[eventName] = dispatchConfig;
 
   var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
   if (phasedRegistrationNames) {
     for (var phaseName in phasedRegistrationNames) {
       if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
         var phasedRegistrationName = phasedRegistrationNames[phaseName];
         publishRegistrationName(phasedRegistrationName, pluginModule, eventName);
@@ -716,17 +366,17 @@ function publishEventForPlugin(dispatchC
 /**
  * Publishes a registration name that is used to identify dispatched events.
  *
  * @param {string} registrationName Registration name to add.
  * @param {object} PluginModule Plugin publishing the event.
  * @private
  */
 function publishRegistrationName(registrationName, pluginModule, eventName) {
-  !!registrationNameModules[registrationName] ? invariant_1(false, 'EventPluginHub: More than one plugin attempted to publish the same registration name, `%s`.', registrationName) : void 0;
+  !!registrationNameModules[registrationName] ? invariant(false, 'EventPluginHub: More than one plugin attempted to publish the same registration name, `%s`.', registrationName) : void 0;
   registrationNameModules[registrationName] = pluginModule;
   registrationNameDependencies[registrationName] = pluginModule.eventTypes[eventName].dependencies;
 
   {
     var lowerCasedName = registrationName.toLowerCase();
     possibleRegistrationNames[lowerCasedName] = registrationName;
 
     if (registrationName === 'onDoubleClick') {
@@ -775,17 +425,17 @@ var possibleRegistrationNames = {};
  * to be decoupled from injection of the actual plugins so that ordering is
  * always deterministic regardless of packaging, on-the-fly injection, etc.
  *
  * @param {array} InjectedEventPluginOrder
  * @internal
  * @see {EventPluginHub.injection.injectEventPluginOrder}
  */
 function injectEventPluginOrder(injectedEventPluginOrder) {
-  !!eventPluginOrder ? invariant_1(false, 'EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React.') : void 0;
+  !!eventPluginOrder ? invariant(false, 'EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React.') : void 0;
   // Clone the ordering so it cannot be dynamically mutated.
   eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
   recomputePluginOrdering();
 }
 
 /**
  * Injects plugins to be used by `EventPluginHub`. The plugin names must be
  * in the ordering injected by `injectEventPluginOrder`.
@@ -799,204 +449,137 @@ function injectEventPluginOrder(injected
 function injectEventPluginsByName(injectedNamesToPlugins) {
   var isOrderingDirty = false;
   for (var pluginName in injectedNamesToPlugins) {
     if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
       continue;
     }
     var pluginModule = injectedNamesToPlugins[pluginName];
     if (!namesToPlugins.hasOwnProperty(pluginName) || namesToPlugins[pluginName] !== pluginModule) {
-      !!namesToPlugins[pluginName] ? invariant_1(false, 'EventPluginRegistry: Cannot inject two different event plugins using the same name, `%s`.', pluginName) : void 0;
+      !!namesToPlugins[pluginName] ? invariant(false, 'EventPluginRegistry: Cannot inject two different event plugins using the same name, `%s`.', pluginName) : void 0;
       namesToPlugins[pluginName] = pluginModule;
       isOrderingDirty = true;
     }
   }
   if (isOrderingDirty) {
     recomputePluginOrdering();
   }
 }
 
-var EventPluginRegistry = Object.freeze({
-	plugins: plugins,
-	eventNameDispatchConfigs: eventNameDispatchConfigs,
-	registrationNameModules: registrationNameModules,
-	registrationNameDependencies: registrationNameDependencies,
-	possibleRegistrationNames: possibleRegistrationNames,
-	injectEventPluginOrder: injectEventPluginOrder,
-	injectEventPluginsByName: injectEventPluginsByName
-});
-
-/**
- * Copyright (c) 2013-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * 
- */
-
-function makeEmptyFunction(arg) {
-  return function () {
-    return arg;
-  };
-}
-
-/**
- * This function accepts and discards inputs; it has no side effects. This is
- * primarily useful idiomatically for overridable function endpoints which
- * always need to be callable, since JS lacks a null-call idiom ala Cocoa.
- */
-var emptyFunction = function emptyFunction() {};
-
-emptyFunction.thatReturns = makeEmptyFunction;
-emptyFunction.thatReturnsFalse = makeEmptyFunction(false);
-emptyFunction.thatReturnsTrue = makeEmptyFunction(true);
-emptyFunction.thatReturnsNull = makeEmptyFunction(null);
-emptyFunction.thatReturnsThis = function () {
-  return this;
-};
-emptyFunction.thatReturnsArgument = function (arg) {
-  return arg;
-};
-
-var emptyFunction_1 = emptyFunction;
-
-/**
- * Copyright (c) 2014-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-
-
-
-
 /**
  * Similar to invariant but only logs a warning if the condition is not met.
  * This can be used to log issues in development environments in critical
  * paths. Removing the logging code for production environments will keep the
  * same logic and follow the same code paths.
  */
 
-var warning = emptyFunction_1;
+var warningWithoutStack = function () {};
 
 {
-  var printWarning = function printWarning(format) {
-    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
-      args[_key - 1] = arguments[_key];
-    }
-
-    var argIndex = 0;
-    var message = 'Warning: ' + format.replace(/%s/g, function () {
-      return args[argIndex++];
-    });
+  warningWithoutStack = function (condition, format) {
+    for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+      args[_key - 2] = arguments[_key];
+    }
+
+    if (format === undefined) {
+      throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning ' + 'message argument');
+    }
+    if (args.length > 8) {
+      // Check before the condition to catch violations early.
+      throw new Error('warningWithoutStack() currently supports at most 8 arguments.');
+    }
+    if (condition) {
+      return;
+    }
     if (typeof console !== 'undefined') {
-      console.error(message);
+      var argsWithFormat = args.map(function (item) {
+        return '' + item;
+      });
+      argsWithFormat.unshift('Warning: ' + format);
+
+      // We intentionally don't use spread (or .apply) directly because it
+      // breaks IE9: https://github.com/facebook/react/issues/13610
+      Function.prototype.apply.call(console.error, console, argsWithFormat);
     }
     try {
       // --- Welcome to debugging React ---
       // This error was thrown as a convenience so that you can use this stack
       // to find the callsite that caused this warning to fire.
+      var argIndex = 0;
+      var message = 'Warning: ' + format.replace(/%s/g, function () {
+        return args[argIndex++];
+      });
       throw new Error(message);
     } catch (x) {}
   };
-
-  warning = function warning(condition, format) {
-    if (format === undefined) {
-      throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
-    }
-
-    if (format.indexOf('Failed Composite propType: ') === 0) {
-      return; // Ignore CompositeComponent proptype check.
-    }
-
-    if (!condition) {
-      for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
-        args[_key2 - 2] = arguments[_key2];
-      }
-
-      printWarning.apply(undefined, [format].concat(args));
-    }
-  };
-}
-
-var warning_1 = warning;
+}
+
+var warningWithoutStack$1 = warningWithoutStack;
 
 var getFiberCurrentPropsFromNode = null;
 var getInstanceFromNode = null;
 var getNodeFromInstance = null;
 
-var injection$2 = {
-  injectComponentTree: function (Injected) {
-    getFiberCurrentPropsFromNode = Injected.getFiberCurrentPropsFromNode;
-    getInstanceFromNode = Injected.getInstanceFromNode;
-    getNodeFromInstance = Injected.getNodeFromInstance;
-
-    {
-      warning_1(getNodeFromInstance && getInstanceFromNode, 'EventPluginUtils.injection.injectComponentTree(...): Injected ' + 'module is missing getNodeFromInstance or getInstanceFromNode.');
-    }
-  }
-};
-
-
-
-
-
-
-var validateEventDispatches;
+function setComponentTree(getFiberCurrentPropsFromNodeImpl, getInstanceFromNodeImpl, getNodeFromInstanceImpl) {
+  getFiberCurrentPropsFromNode = getFiberCurrentPropsFromNodeImpl;
+  getInstanceFromNode = getInstanceFromNodeImpl;
+  getNodeFromInstance = getNodeFromInstanceImpl;
+  {
+    !(getNodeFromInstance && getInstanceFromNode) ? warningWithoutStack$1(false, 'EventPluginUtils.setComponentTree(...): Injected ' + 'module is missing getNodeFromInstance or getInstanceFromNode.') : void 0;
+  }
+}
+
+var validateEventDispatches = void 0;
 {
   validateEventDispatches = function (event) {
     var dispatchListeners = event._dispatchListeners;
     var dispatchInstances = event._dispatchInstances;
 
     var listenersIsArr = Array.isArray(dispatchListeners);
     var listenersLen = listenersIsArr ? dispatchListeners.length : dispatchListeners ? 1 : 0;
 
     var instancesIsArr = Array.isArray(dispatchInstances);
     var instancesLen = instancesIsArr ? dispatchInstances.length : dispatchInstances ? 1 : 0;
 
-    warning_1(instancesIsArr === listenersIsArr && instancesLen === listenersLen, 'EventPluginUtils: Invalid `event`.');
+    !(instancesIsArr === listenersIsArr && instancesLen === listenersLen) ? warningWithoutStack$1(false, 'EventPluginUtils: Invalid `event`.') : void 0;
   };
 }
 
 /**
  * Dispatch the event to the listener.
  * @param {SyntheticEvent} event SyntheticEvent to handle
- * @param {boolean} simulated If the event is simulated (changes exn behavior)
  * @param {function} listener Application-level callback
  * @param {*} inst Internal component instance
  */
-function executeDispatch(event, simulated, listener, inst) {
+function executeDispatch(event, listener, inst) {
   var type = event.type || 'unknown-event';
   event.currentTarget = getNodeFromInstance(inst);
-  ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
+  invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
   event.currentTarget = null;
 }
 
 /**
  * Standard/simple iteration through an event's collected dispatches.
  */
-function executeDispatchesInOrder(event, simulated) {
+function executeDispatchesInOrder(event) {
   var dispatchListeners = event._dispatchListeners;
   var dispatchInstances = event._dispatchInstances;
   {
     validateEventDispatches(event);
   }
   if (Array.isArray(dispatchListeners)) {
     for (var i = 0; i < dispatchListeners.length; i++) {
       if (event.isPropagationStopped()) {
         break;
       }
       // Listeners and Instances are two parallel arrays that are always in sync.
-      executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
+      executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
     }
   } else if (dispatchListeners) {
-    executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
+    executeDispatch(event, dispatchListeners, dispatchInstances);
   }
   event._dispatchListeners = null;
   event._dispatchInstances = null;
 }
 
 /**
  * @see executeDispatchesInOrderStopAtTrueImpl
  */
@@ -1027,17 +610,17 @@ function executeDispatchesInOrder(event,
  * `a = accumulateInto(a, b);`
  *
  * This API should be sparingly used. Try `accumulate` for something cleaner.
  *
  * @return {*|array<*>} An accumulation of items.
  */
 
 function accumulateInto(current, next) {
-  !(next != null) ? invariant_1(false, 'accumulateInto(...): Accumulated items must not be null or undefined.') : void 0;
+  !(next != null) ? invariant(false, 'accumulateInto(...): Accumulated items must not be null or undefined.') : void 0;
 
   if (current == null) {
     return next;
   }
 
   // Both are not empty. Warning: Never call x.concat(y) when you are not
   // certain that x is an Array (x could be a string with concat method).
   if (Array.isArray(current)) {
@@ -1079,33 +662,29 @@ function forEachAccumulated(arr, cb, sco
  * waiting to have their dispatches executed.
  */
 var eventQueue = null;
 
 /**
  * Dispatches an event and releases it back into the pool, unless persistent.
  *
  * @param {?object} event Synthetic event to be dispatched.
- * @param {boolean} simulated If the event is simulated (changes exn behavior)
  * @private
  */
-var executeDispatchesAndRelease = function (event, simulated) {
+var executeDispatchesAndRelease = function (event) {
   if (event) {
-    executeDispatchesInOrder(event, simulated);
+    executeDispatchesInOrder(event);
 
     if (!event.isPersistent()) {
       event.constructor.release(event);
     }
   }
 };
-var executeDispatchesAndReleaseSimulated = function (e) {
-  return executeDispatchesAndRelease(e, true);
-};
 var executeDispatchesAndReleaseTopLevel = function (e) {
-  return executeDispatchesAndRelease(e, false);
+  return executeDispatchesAndRelease(e);
 };
 
 function isInteractive(tag) {
   return tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea';
 }
 
 function shouldPreventMouseEvent(name, type, props) {
   switch (name) {
@@ -1146,17 +725,17 @@ function shouldPreventMouseEvent(name, t
  * Each plugin that is injected into `EventsPluginHub` is immediately operable.
  *
  * @public
  */
 
 /**
  * Methods for injecting dependencies.
  */
-var injection$1 = {
+var injection = {
   /**
    * @param {array} InjectedEventPluginOrder
    * @public
    */
   injectEventPluginOrder: injectEventPluginOrder,
 
   /**
    * @param {object} injectedNamesToPlugins Map from names to plugin modules.
@@ -1165,17 +744,17 @@ var injection$1 = {
 };
 
 /**
  * @param {object} inst The instance, which is the source of events.
  * @param {string} registrationName Name of listener (e.g. `onClick`).
  * @return {?function} The stored callback.
  */
 function getListener(inst, registrationName) {
-  var listener;
+  var listener = void 0;
 
   // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
   // live here; needs to be moved to a better place soon
   var stateNode = inst.stateNode;
   if (!stateNode) {
     // Work in progress (ex: onload events in incremental mode).
     return null;
   }
@@ -1183,141 +762,121 @@ function getListener(inst, registrationN
   if (!props) {
     // Work in progress.
     return null;
   }
   listener = props[registrationName];
   if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
     return null;
   }
-  !(!listener || typeof listener === 'function') ? invariant_1(false, 'Expected `%s` listener to be a function, instead got a value of `%s` type.', registrationName, typeof listener) : void 0;
+  !(!listener || typeof listener === 'function') ? invariant(false, 'Expected `%s` listener to be a function, instead got a value of `%s` type.', registrationName, typeof listener) : void 0;
   return listener;
 }
 
 /**
  * Allows registered plugins an opportunity to extract events from top-level
  * native browser events.
  *
  * @return {*} An accumulation of synthetic events.
  * @internal
  */
 function extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
-  var events;
+  var events = null;
   for (var i = 0; i < plugins.length; i++) {
     // Not every plugin in the ordering may be loaded at runtime.
     var possiblePlugin = plugins[i];
     if (possiblePlugin) {
       var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
       if (extractedEvents) {
         events = accumulateInto(events, extractedEvents);
       }
     }
   }
   return events;
 }
 
-/**
- * Enqueues a synthetic event that should be dispatched when
- * `processEventQueue` is invoked.
- *
- * @param {*} events An accumulation of synthetic events.
- * @internal
- */
-function enqueueEvents(events) {
-  if (events) {
+function runEventsInBatch(events) {
+  if (events !== null) {
     eventQueue = accumulateInto(eventQueue, events);
   }
-}
-
-/**
- * Dispatches all synthetic events on the event queue.
- *
- * @internal
- */
-function processEventQueue(simulated) {
+
   // Set `eventQueue` to null before processing it so that we can tell if more
   // events get enqueued while processing.
   var processingEventQueue = eventQueue;
   eventQueue = null;
 
   if (!processingEventQueue) {
     return;
   }
 
-  if (simulated) {
-    forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
-  } else {
-    forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
-  }
-  !!eventQueue ? invariant_1(false, 'processEventQueue(): Additional events were enqueued while processing an event queue. Support for this has not yet been implemented.') : void 0;
+  forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
+  !!eventQueue ? invariant(false, 'processEventQueue(): Additional events were enqueued while processing an event queue. Support for this has not yet been implemented.') : void 0;
   // This would be a good time to rethrow if any of the event handlers threw.
-  ReactErrorUtils.rethrowCaughtError();
-}
-
-var EventPluginHub = Object.freeze({
-	injection: injection$1,
-	getListener: getListener,
-	extractEvents: extractEvents,
-	enqueueEvents: enqueueEvents,
-	processEventQueue: processEventQueue
-});
-
-var IndeterminateComponent = 0; // Before we know whether it is functional or class
-var FunctionalComponent = 1;
-var ClassComponent = 2;
+  rethrowCaughtError();
+}
+
+function runExtractedEventsInBatch(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
+  var events = extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
+  runEventsInBatch(events);
+}
+
+var FunctionComponent = 0;
+var ClassComponent = 1;
+var IndeterminateComponent = 2; // Before we know whether it is function or class
 var HostRoot = 3; // Root of a host tree. Could be nested inside another node.
 var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
 var HostComponent = 5;
 var HostText = 6;
-var CallComponent = 7;
-var CallHandlerPhase = 8;
-var ReturnComponent = 9;
-var Fragment = 10;
+var Fragment = 7;
+var Mode = 8;
+var ContextConsumer = 9;
+var ContextProvider = 10;
+var ForwardRef = 11;
+var Profiler = 12;
+var SuspenseComponent = 13;
+var MemoComponent = 14;
+var SimpleMemoComponent = 15;
+var LazyComponent = 16;
+var IncompleteClassComponent = 17;
+var DehydratedSuspenseComponent = 18;
 
 var randomKey = Math.random().toString(36).slice(2);
 var internalInstanceKey = '__reactInternalInstance$' + randomKey;
 var internalEventHandlersKey = '__reactEventHandlers$' + randomKey;
 
-function precacheFiberNode$1(hostInst, node) {
+function precacheFiberNode(hostInst, node) {
   node[internalInstanceKey] = hostInst;
 }
 
 /**
  * Given a DOM node, return the closest ReactDOMComponent or
  * ReactDOMTextComponent instance ancestor.
  */
 function getClosestInstanceFromNode(node) {
   if (node[internalInstanceKey]) {
     return node[internalInstanceKey];
   }
 
-  // Walk up the tree until we find an ancestor whose instance we have cached.
-  var parents = [];
   while (!node[internalInstanceKey]) {
-    parents.push(node);
     if (node.parentNode) {
       node = node.parentNode;
     } else {
       // Top of the tree. This node must not be part of a React tree (or is
       // unmounted, potentially).
       return null;
     }
   }
 
-  var closest = void 0;
   var inst = node[internalInstanceKey];
   if (inst.tag === HostComponent || inst.tag === HostText) {
     // In Fiber, this will always be the deepest root.
     return inst;
   }
-  for (; node && (inst = node[internalInstanceKey]); node = parents.pop()) {
-    closest = inst;
-  }
-
-  return closest;
+
+  return null;
 }
 
 /**
  * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
  * instance, or null if the node was not rendered by this React.
  */
 function getInstanceFromNode$1(node) {
   var inst = node[internalInstanceKey];
@@ -1339,39 +898,30 @@ function getNodeFromInstance$1(inst) {
   if (inst.tag === HostComponent || inst.tag === HostText) {
     // In Fiber this, is just the state node right now. We assume it will be
     // a host component or host text.
     return inst.stateNode;
   }
 
   // Without this first invariant, passing a non-DOM-component triggers the next
   // invariant for a missing parent, which is super confusing.
-  invariant_1(false, 'getNodeFromInstance: Invalid argument.');
+  invariant(false, 'getNodeFromInstance: Invalid argument.');
 }
 
 function getFiberCurrentPropsFromNode$1(node) {
   return node[internalEventHandlersKey] || null;
 }
 
-function updateFiberProps$1(node, props) {
+function updateFiberProps(node, props) {
   node[internalEventHandlersKey] = props;
 }
 
-var ReactDOMComponentTree = Object.freeze({
-	precacheFiberNode: precacheFiberNode$1,
-	getClosestInstanceFromNode: getClosestInstanceFromNode,
-	getInstanceFromNode: getInstanceFromNode$1,
-	getNodeFromInstance: getNodeFromInstance$1,
-	getFiberCurrentPropsFromNode: getFiberCurrentPropsFromNode$1,
-	updateFiberProps: updateFiberProps$1
-});
-
 function getParent(inst) {
   do {
-    inst = inst['return'];
+    inst = inst.return;
     // TODO: If this is a HostRoot we might want to bail out.
     // That is depending on if we want nested subtrees (layers) to bubble
     // events to their parent. We could also go through parentNode on the
     // host node but that wouldn't work for React Native and doesn't let us
     // do the portal feature.
   } while (inst && inst.tag !== HostComponent);
   if (inst) {
     return inst;
@@ -1420,30 +970,28 @@ function getLowestCommonAncestor(instA, 
 /**
  * Return if A is an ancestor of B.
  */
 
 
 /**
  * Return the parent instance of the passed-in instance.
  */
-function getParentInstance(inst) {
-  return getParent(inst);
-}
+
 
 /**
  * Simulates the traversal of a two-phase, capture/bubble event dispatch.
  */
 function traverseTwoPhase(inst, fn, arg) {
   var path = [];
   while (inst) {
     path.push(inst);
     inst = getParent(inst);
   }
-  var i;
+  var i = void 0;
   for (i = path.length; i-- > 0;) {
     fn(path[i], 'captured', arg);
   }
   for (i = 0; i < path.length; i++) {
     fn(path[i], 'bubbled', arg);
   }
 }
 
@@ -1516,17 +1064,17 @@ function listenerAtPhase(inst, event, pr
 /**
  * Tags a `SyntheticEvent` with dispatched listeners. Creating this function
  * here, allows us to not have to bind or create functions for each event.
  * Mutating the event's members allows us to not have to create a wrapping
  * "dispatch" object that pairs the event with the listener.
  */
 function accumulateDirectionalDispatches(inst, phase, event) {
   {
-    warning_1(inst, 'Dispatching inst must not be null');
+    !inst ? warningWithoutStack$1(false, 'Dispatching inst must not be null') : void 0;
   }
   var listener = listenerAtPhase(inst, event, phase);
   if (listener) {
     event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
     event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
   }
 }
 
@@ -1539,27 +1087,16 @@ function accumulateDirectionalDispatches
  */
 function accumulateTwoPhaseDispatchesSingle(event) {
   if (event && event.dispatchConfig.phasedRegistrationNames) {
     traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
   }
 }
 
 /**
- * Same as `accumulateTwoPhaseDispatchesSingle`, but skips over the targetID.
- */
-function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
-  if (event && event.dispatchConfig.phasedRegistrationNames) {
-    var targetInst = event._targetInst;
-    var parentInst = targetInst ? getParentInstance(targetInst) : null;
-    traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
-  }
-}
-
-/**
  * Accumulates without regard to direction, does not look for phased
  * registration names. Same as `accumulateDirectDispatchesSingle` but without
  * requiring that the `dispatchMarker` be the same as the dispatched ID.
  */
 function accumulateDispatches(inst, ignoredDirection, event) {
   if (inst && event && event.dispatchConfig.registrationName) {
     var registrationName = event.dispatchConfig.registrationName;
     var listener = getListener(inst, registrationName);
@@ -1580,124 +1117,259 @@ function accumulateDirectDispatchesSingl
     accumulateDispatches(event._targetInst, null, event);
   }
 }
 
 function accumulateTwoPhaseDispatches(events) {
   forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
 }
 
-function accumulateTwoPhaseDispatchesSkipTarget(events) {
-  forEachAccumulated(events, accumulateTwoPhaseDispatchesSingleSkipTarget);
-}
+
 
 function accumulateEnterLeaveDispatches(leave, enter, from, to) {
   traverseEnterLeave(from, to, accumulateDispatches, leave, enter);
 }
 
 function accumulateDirectDispatches(events) {
   forEachAccumulated(events, accumulateDirectDispatchesSingle);
 }
 
-var EventPropagators = Object.freeze({
-	accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches,
-	accumulateTwoPhaseDispatchesSkipTarget: accumulateTwoPhaseDispatchesSkipTarget,
-	accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches,
-	accumulateDirectDispatches: accumulateDirectDispatches
-});
-
-/**
- * Copyright (c) 2013-present, Facebook, Inc.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-
-
 var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
 
-/**
- * Simple, lightweight module assisting with the detection and context of
- * Worker. Helps avoid circular dependencies and allows code to reason about
- * whether or not they are in a Worker, even if they never include the main
- * `ReactWorker` dependency.
- */
-var ExecutionEnvironment = {
-
-  canUseDOM: canUseDOM,
-
-  canUseWorkers: typeof Worker !== 'undefined',
-
-  canUseEventListeners: canUseDOM && !!(window.addEventListener || window.attachEvent),
-
-  canUseViewport: canUseDOM && !!window.screen,
-
-  isInWorker: !canUseDOM // For now, this is true - might change in the future.
-
-};
-
-var ExecutionEnvironment_1 = ExecutionEnvironment;
-
-var contentKey = null;
-
-/**
- * Gets the key used to access text content on a DOM node.
- *
- * @return {?string} Key used to access text content.
- * @internal
- */
-function getTextContentAccessor() {
-  if (!contentKey && ExecutionEnvironment_1.canUseDOM) {
-    // Prefer textContent to innerText because many browsers support both but
-    // SVG <text> elements don't support innerText even when <div> does.
-    contentKey = 'textContent' in document.documentElement ? 'textContent' : 'innerText';
-  }
-  return contentKey;
-}
-
-/**
- * This helper object stores information about text content of a target node,
+// Do not uses the below two methods directly!
+// Instead use constants exported from DOMTopLevelEventTypes in ReactDOM.
+// (It is the only module that is allowed to access these methods.)
+
+function unsafeCastStringToDOMTopLevelType(topLevelType) {
+  return topLevelType;
+}
+
+function unsafeCastDOMTopLevelTypeToString(topLevelType) {
+  return topLevelType;
+}
+
+/**
+ * Generate a mapping of standard vendor prefixes using the defined style property and event name.
+ *
+ * @param {string} styleProp
+ * @param {string} eventName
+ * @returns {object}
+ */
+function makePrefixMap(styleProp, eventName) {
+  var prefixes = {};
+
+  prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
+  prefixes['Webkit' + styleProp] = 'webkit' + eventName;
+  prefixes['Moz' + styleProp] = 'moz' + eventName;
+
+  return prefixes;
+}
+
+/**
+ * A list of event names to a configurable list of vendor prefixes.
+ */
+var vendorPrefixes = {
+  animationend: makePrefixMap('Animation', 'AnimationEnd'),
+  animationiteration: makePrefixMap('Animation', 'AnimationIteration'),
+  animationstart: makePrefixMap('Animation', 'AnimationStart'),
+  transitionend: makePrefixMap('Transition', 'TransitionEnd')
+};
+
+/**
+ * Event names that have already been detected and prefixed (if applicable).
+ */
+var prefixedEventNames = {};
+
+/**
+ * Element to check for prefixes on.
+ */
+var style = {};
+
+/**
+ * Bootstrap if a DOM exists.
+ */
+if (canUseDOM) {
+  style = document.createElement('div').style;
+
+  // On some platforms, in particular some releases of Android 4.x,
+  // the un-prefixed "animation" and "transition" properties are defined on the
+  // style object but the events that fire will still be prefixed, so we need
+  // to check if the un-prefixed events are usable, and if not remove them from the map.
+  if (!('AnimationEvent' in window)) {
+    delete vendorPrefixes.animationend.animation;
+    delete vendorPrefixes.animationiteration.animation;
+    delete vendorPrefixes.animationstart.animation;
+  }
+
+  // Same as above
+  if (!('TransitionEvent' in window)) {
+    delete vendorPrefixes.transitionend.transition;
+  }
+}
+
+/**
+ * Attempts to determine the correct vendor prefixed event name.
+ *
+ * @param {string} eventName
+ * @returns {string}
+ */
+function getVendorPrefixedEventName(eventName) {
+  if (prefixedEventNames[eventName]) {
+    return prefixedEventNames[eventName];
+  } else if (!vendorPrefixes[eventName]) {
+    return eventName;
+  }
+
+  var prefixMap = vendorPrefixes[eventName];
+
+  for (var styleProp in prefixMap) {
+    if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) {
+      return prefixedEventNames[eventName] = prefixMap[styleProp];
+    }
+  }
+
+  return eventName;
+}
+
+/**
+ * To identify top level events in ReactDOM, we use constants defined by this
+ * module. This is the only module that uses the unsafe* methods to express
+ * that the constants actually correspond to the browser event names. This lets
+ * us save some bundle size by avoiding a top level type -> event name map.
+ * The rest of ReactDOM code should import top level types from this file.
+ */
+var TOP_ABORT = unsafeCastStringToDOMTopLevelType('abort');
+var TOP_ANIMATION_END = unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('animationend'));
+var TOP_ANIMATION_ITERATION = unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('animationiteration'));
+var TOP_ANIMATION_START = unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('animationstart'));
+var TOP_BLUR = unsafeCastStringToDOMTopLevelType('blur');
+var TOP_CAN_PLAY = unsafeCastStringToDOMTopLevelType('canplay');
+var TOP_CAN_PLAY_THROUGH = unsafeCastStringToDOMTopLevelType('canplaythrough');
+var TOP_CANCEL = unsafeCastStringToDOMTopLevelType('cancel');
+var TOP_CHANGE = unsafeCastStringToDOMTopLevelType('change');
+var TOP_CLICK = unsafeCastStringToDOMTopLevelType('click');
+var TOP_CLOSE = unsafeCastStringToDOMTopLevelType('close');
+var TOP_COMPOSITION_END = unsafeCastStringToDOMTopLevelType('compositionend');
+var TOP_COMPOSITION_START = unsafeCastStringToDOMTopLevelType('compositionstart');
+var TOP_COMPOSITION_UPDATE = unsafeCastStringToDOMTopLevelType('compositionupdate');
+var TOP_CONTEXT_MENU = unsafeCastStringToDOMTopLevelType('contextmenu');
+var TOP_COPY = unsafeCastStringToDOMTopLevelType('copy');
+var TOP_CUT = unsafeCastStringToDOMTopLevelType('cut');
+var TOP_DOUBLE_CLICK = unsafeCastStringToDOMTopLevelType('dblclick');
+var TOP_AUX_CLICK = unsafeCastStringToDOMTopLevelType('auxclick');
+var TOP_DRAG = unsafeCastStringToDOMTopLevelType('drag');
+var TOP_DRAG_END = unsafeCastStringToDOMTopLevelType('dragend');
+var TOP_DRAG_ENTER = unsafeCastStringToDOMTopLevelType('dragenter');
+var TOP_DRAG_EXIT = unsafeCastStringToDOMTopLevelType('dragexit');
+var TOP_DRAG_LEAVE = unsafeCastStringToDOMTopLevelType('dragleave');
+var TOP_DRAG_OVER = unsafeCastStringToDOMTopLevelType('dragover');
+var TOP_DRAG_START = unsafeCastStringToDOMTopLevelType('dragstart');
+var TOP_DROP = unsafeCastStringToDOMTopLevelType('drop');
+var TOP_DURATION_CHANGE = unsafeCastStringToDOMTopLevelType('durationchange');
+var TOP_EMPTIED = unsafeCastStringToDOMTopLevelType('emptied');
+var TOP_ENCRYPTED = unsafeCastStringToDOMTopLevelType('encrypted');
+var TOP_ENDED = unsafeCastStringToDOMTopLevelType('ended');
+var TOP_ERROR = unsafeCastStringToDOMTopLevelType('error');
+var TOP_FOCUS = unsafeCastStringToDOMTopLevelType('focus');
+var TOP_GOT_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType('gotpointercapture');
+var TOP_INPUT = unsafeCastStringToDOMTopLevelType('input');
+var TOP_INVALID = unsafeCastStringToDOMTopLevelType('invalid');
+var TOP_KEY_DOWN = unsafeCastStringToDOMTopLevelType('keydown');
+var TOP_KEY_PRESS = unsafeCastStringToDOMTopLevelType('keypress');
+var TOP_KEY_UP = unsafeCastStringToDOMTopLevelType('keyup');
+var TOP_LOAD = unsafeCastStringToDOMTopLevelType('load');
+var TOP_LOAD_START = unsafeCastStringToDOMTopLevelType('loadstart');
+var TOP_LOADED_DATA = unsafeCastStringToDOMTopLevelType('loadeddata');
+var TOP_LOADED_METADATA = unsafeCastStringToDOMTopLevelType('loadedmetadata');
+var TOP_LOST_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType('lostpointercapture');
+var TOP_MOUSE_DOWN = unsafeCastStringToDOMTopLevelType('mousedown');
+var TOP_MOUSE_MOVE = unsafeCastStringToDOMTopLevelType('mousemove');
+var TOP_MOUSE_OUT = unsafeCastStringToDOMTopLevelType('mouseout');
+var TOP_MOUSE_OVER = unsafeCastStringToDOMTopLevelType('mouseover');
+var TOP_MOUSE_UP = unsafeCastStringToDOMTopLevelType('mouseup');
+var TOP_PASTE = unsafeCastStringToDOMTopLevelType('paste');
+var TOP_PAUSE = unsafeCastStringToDOMTopLevelType('pause');
+var TOP_PLAY = unsafeCastStringToDOMTopLevelType('play');
+var TOP_PLAYING = unsafeCastStringToDOMTopLevelType('playing');
+var TOP_POINTER_CANCEL = unsafeCastStringToDOMTopLevelType('pointercancel');
+var TOP_POINTER_DOWN = unsafeCastStringToDOMTopLevelType('pointerdown');
+
+
+var TOP_POINTER_MOVE = unsafeCastStringToDOMTopLevelType('pointermove');
+var TOP_POINTER_OUT = unsafeCastStringToDOMTopLevelType('pointerout');
+var TOP_POINTER_OVER = unsafeCastStringToDOMTopLevelType('pointerover');
+var TOP_POINTER_UP = unsafeCastStringToDOMTopLevelType('pointerup');
+var TOP_PROGRESS = unsafeCastStringToDOMTopLevelType('progress');
+var TOP_RATE_CHANGE = unsafeCastStringToDOMTopLevelType('ratechange');
+var TOP_RESET = unsafeCastStringToDOMTopLevelType('reset');
+var TOP_SCROLL = unsafeCastStringToDOMTopLevelType('scroll');
+var TOP_SEEKED = unsafeCastStringToDOMTopLevelType('seeked');
+var TOP_SEEKING = unsafeCastStringToDOMTopLevelType('seeking');
+var TOP_SELECTION_CHANGE = unsafeCastStringToDOMTopLevelType('selectionchange');
+var TOP_STALLED = unsafeCastStringToDOMTopLevelType('stalled');
+var TOP_SUBMIT = unsafeCastStringToDOMTopLevelType('submit');
+var TOP_SUSPEND = unsafeCastStringToDOMTopLevelType('suspend');
+var TOP_TEXT_INPUT = unsafeCastStringToDOMTopLevelType('textInput');
+var TOP_TIME_UPDATE = unsafeCastStringToDOMTopLevelType('timeupdate');
+var TOP_TOGGLE = unsafeCastStringToDOMTopLevelType('toggle');
+var TOP_TOUCH_CANCEL = unsafeCastStringToDOMTopLevelType('touchcancel');
+var TOP_TOUCH_END = unsafeCastStringToDOMTopLevelType('touchend');
+var TOP_TOUCH_MOVE = unsafeCastStringToDOMTopLevelType('touchmove');
+var TOP_TOUCH_START = unsafeCastStringToDOMTopLevelType('touchstart');
+var TOP_TRANSITION_END = unsafeCastStringToDOMTopLevelType(getVendorPrefixedEventName('transitionend'));
+var TOP_VOLUME_CHANGE = unsafeCastStringToDOMTopLevelType('volumechange');
+var TOP_WAITING = unsafeCastStringToDOMTopLevelType('waiting');
+var TOP_WHEEL = unsafeCastStringToDOMTopLevelType('wheel');
+
+// List of events that need to be individually attached to media elements.
+// Note that events in this list will *not* be listened to at the top level
+// unless they're explicitly whitelisted in `ReactBrowserEventEmitter.listenTo`.
+var mediaEventTypes = [TOP_ABORT, TOP_CAN_PLAY, TOP_CAN_PLAY_THROUGH, TOP_DURATION_CHANGE, TOP_EMPTIED, TOP_ENCRYPTED, TOP_ENDED, TOP_ERROR, TOP_LOADED_DATA, TOP_LOADED_METADATA, TOP_LOAD_START, TOP_PAUSE, TOP_PLAY, TOP_PLAYING, TOP_PROGRESS, TOP_RATE_CHANGE, TOP_SEEKED, TOP_SEEKING, TOP_STALLED, TOP_SUSPEND, TOP_TIME_UPDATE, TOP_VOLUME_CHANGE, TOP_WAITING];
+
+function getRawEventName(topLevelType) {
+  return unsafeCastDOMTopLevelTypeToString(topLevelType);
+}
+
+/**
+ * These variables store information about text content of a target node,
  * allowing comparison of content before and after a given event.
  *
  * Identify the node where selection currently begins, then observe
  * both its text content and its current position in the DOM. Since the
  * browser may natively replace the target node during composition, we can
  * use its position to find its replacement.
  *
  *
  */
-var compositionState = {
-  _root: null,
-  _startText: null,
-  _fallbackText: null
-};
+
+var root = null;
+var startText = null;
+var fallbackText = null;
 
 function initialize(nativeEventTarget) {
-  compositionState._root = nativeEventTarget;
-  compositionState._startText = getText();
+  root = nativeEventTarget;
+  startText = getText();
   return true;
 }
 
 function reset() {
-  compositionState._root = null;
-  compositionState._startText = null;
-  compositionState._fallbackText = null;
+  root = null;
+  startText = null;
+  fallbackText = null;
 }
 
 function getData() {
-  if (compositionState._fallbackText) {
-    return compositionState._fallbackText;
-  }
-
-  var start;
-  var startValue = compositionState._startText;
+  if (fallbackText) {
+    return fallbackText;
+  }
+
+  var start = void 0;
+  var startValue = startText;
   var startLength = startValue.length;
-  var end;
+  var end = void 0;
   var endValue = getText();
   var endLength = endValue.length;
 
   for (start = 0; start < startLength; start++) {
     if (startValue[start] !== endValue[start]) {
       break;
     }
   }
@@ -1705,58 +1377,64 @@ function getData() {
   var minEnd = startLength - start;
   for (end = 1; end <= minEnd; end++) {
     if (startValue[startLength - end] !== endValue[endLength - end]) {
       break;
     }
   }
 
   var sliceTail = end > 1 ? 1 - end : undefined;
-  compositionState._fallbackText = endValue.slice(start, sliceTail);
-  return compositionState._fallbackText;
+  fallbackText = endValue.slice(start, sliceTail);
+  return fallbackText;
 }
 
 function getText() {
-  if ('value' in compositionState._root) {
-    return compositionState._root.value;
-  }
-  return compositionState._root[getTextContentAccessor()];
+  if ('value' in root) {
+    return root.value;
+  }
+  return root.textContent;
 }
 
 var ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
 
 var _assign = ReactInternals.assign;
 
 /* eslint valid-typeof: 0 */
 
-var didWarnForAddedNewProperty = false;
-var isProxySupported = typeof Proxy === 'function';
 var EVENT_POOL_SIZE = 10;
 
-var shouldBeReleasedProperties = ['dispatchConfig', '_targetInst', 'nativeEvent', 'isDefaultPrevented', 'isPropagationStopped', '_dispatchListeners', '_dispatchInstances'];
-
 /**
  * @interface Event
  * @see http://www.w3.org/TR/DOM-Level-3-Events/
  */
 var EventInterface = {
   type: null,
   target: null,
   // currentTarget is set when dispatching; no use in copying it here
-  currentTarget: emptyFunction_1.thatReturnsNull,
+  currentTarget: function () {
+    return null;
+  },
   eventPhase: null,
   bubbles: null,
   cancelable: null,
   timeStamp: function (event) {
     return event.timeStamp || Date.now();
   },
   defaultPrevented: null,
   isTrusted: null
 };
 
+function functionThatReturnsTrue() {
+  return true;
+}
+
+function functionThatReturnsFalse() {
+  return false;
+}
+
 /**
  * Synthetic events are dispatched by event plugins, typically in response to a
  * top-level event delegation handler.
  *
  * These systems should generally use pooling to reduce the frequency of garbage
  * collection. The system should check `isPersistent` to determine whether the
  * event should be released into the pool after being dispatched. Users that
  * need a persisted event should invoke `persist`.
@@ -1771,16 +1449,18 @@ var EventInterface = {
  * @param {DOMEventTarget} nativeEventTarget Target node.
  */
 function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
   {
     // these have a getter/setter for warnings
     delete this.nativeEvent;
     delete this.preventDefault;
     delete this.stopPropagation;
+    delete this.isDefaultPrevented;
+    delete this.isPropagationStopped;
   }
 
   this.dispatchConfig = dispatchConfig;
   this._targetInst = targetInst;
   this.nativeEvent = nativeEvent;
 
   var Interface = this.constructor.Interface;
   for (var propName in Interface) {
@@ -1799,38 +1479,38 @@ function SyntheticEvent(dispatchConfig, 
       } else {
         this[propName] = nativeEvent[propName];
       }
     }
   }
 
   var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;
   if (defaultPrevented) {
-    this.isDefaultPrevented = emptyFunction_1.thatReturnsTrue;
-  } else {
-    this.isDefaultPrevented = emptyFunction_1.thatReturnsFalse;
-  }
-  this.isPropagationStopped = emptyFunction_1.thatReturnsFalse;
+    this.isDefaultPrevented = functionThatReturnsTrue;
+  } else {
+    this.isDefaultPrevented = functionThatReturnsFalse;
+  }
+  this.isPropagationStopped = functionThatReturnsFalse;
   return this;
 }
 
 _assign(SyntheticEvent.prototype, {
   preventDefault: function () {
     this.defaultPrevented = true;
     var event = this.nativeEvent;