Bug 1551751 - Add return telemetry, faster stories and bug fixes to Activity Stream r=r1cky
authorEd Lee <edilee@mozilla.com>
Wed, 15 May 2019 02:21:12 +0000
changeset 535831 02f031c0f789c7b8c21ad281cddad59ba4a9a8a9
parent 535830 57ad9770bc01ffdbc52f841d8e437820a8a222f1
child 535832 69c5ef5e60352efbfb03e562eb77c9f30ac32690
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersr1cky
bugs1551751
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1551751 - Add return telemetry, faster stories and bug fixes to Activity Stream r=r1cky Differential Revision: https://phabricator.services.mozilla.com/D31174
browser/components/newtab/common/Actions.jsm
browser/components/newtab/content-src/asrouter/asrouter-content.jsx
browser/components/newtab/content-src/asrouter/components/ModalOverlay/ModalOverlay.jsx
browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.jsx
browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.scss
browser/components/newtab/css/activity-stream-linux.css
browser/components/newtab/css/activity-stream-mac.css
browser/components/newtab/css/activity-stream-windows.css
browser/components/newtab/data/content/activity-stream.bundle.js
browser/components/newtab/docs/v2-system-addon/data_events.md
browser/components/newtab/lib/ASRouter.jsm
browser/components/newtab/lib/ASRouterPreferences.jsm
browser/components/newtab/lib/AboutPreferences.jsm
browser/components/newtab/lib/ActivityStream.jsm
browser/components/newtab/lib/DiscoveryStreamFeed.jsm
browser/components/newtab/lib/TopStoriesFeed.jsm
browser/components/newtab/locales-src/de/strings.properties
browser/components/newtab/locales-src/it/strings.properties
browser/components/newtab/locales-src/ka/strings.properties
browser/components/newtab/locales-src/lt/strings.properties
browser/components/newtab/locales-src/sk/strings.properties
browser/components/newtab/locales-src/vi/strings.properties
browser/components/newtab/prerendered/locales/de/activity-stream-strings.js
browser/components/newtab/prerendered/locales/it/activity-stream-strings.js
browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered-noscripts.html
browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered.html
browser/components/newtab/prerendered/locales/ka/activity-stream-strings.js
browser/components/newtab/prerendered/locales/lt/activity-stream-strings.js
browser/components/newtab/prerendered/locales/vi/activity-stream-strings.js
browser/components/newtab/test/unit/asrouter/ASRouter.test.js
browser/components/newtab/test/unit/asrouter/ASRouterPreferences.test.js
browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
browser/components/newtab/test/unit/lib/AboutPreferences.test.js
browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
browser/components/newtab/test/unit/lib/TopStoriesFeed.test.js
--- a/browser/components/newtab/common/Actions.jsm
+++ b/browser/components/newtab/common/Actions.jsm
@@ -25,16 +25,17 @@ this.globalImportContext = globalImportC
 // }
 const actionTypes = {};
 for (const type of [
   "ADDONS_INFO_REQUEST",
   "ADDONS_INFO_RESPONSE",
   "ARCHIVE_FROM_POCKET",
   "AS_ROUTER_INITIALIZED",
   "AS_ROUTER_PREF_CHANGED",
+  "AS_ROUTER_TARGETING_UPDATE",
   "AS_ROUTER_TELEMETRY_USER_EVENT",
   "BLOCK_URL",
   "BOOKMARK_URL",
   "COPY_DOWNLOAD_LINK",
   "DELETE_BOOKMARK_BY_ID",
   "DELETE_FROM_POCKET",
   "DELETE_HISTORY_URL",
   "DIALOG_CANCEL",
@@ -44,17 +45,16 @@ for (const type of [
   "DISCOVERY_STREAM_CONFIG_SET_VALUE",
   "DISCOVERY_STREAM_FEEDS_UPDATE",
   "DISCOVERY_STREAM_FEED_UPDATE",
   "DISCOVERY_STREAM_IMPRESSION_STATS",
   "DISCOVERY_STREAM_LAYOUT_RESET",
   "DISCOVERY_STREAM_LAYOUT_UPDATE",
   "DISCOVERY_STREAM_LINK_BLOCKED",
   "DISCOVERY_STREAM_LOADED_CONTENT",
-  "DISCOVERY_STREAM_OPT_OUT",
   "DISCOVERY_STREAM_SPOCS_CAPS",
   "DISCOVERY_STREAM_SPOCS_ENDPOINT",
   "DISCOVERY_STREAM_SPOCS_FILL",
   "DISCOVERY_STREAM_SPOCS_UPDATE",
   "DISCOVERY_STREAM_SPOC_IMPRESSION",
   "DOWNLOAD_CHANGED",
   "FAKE_FOCUS_SEARCH",
   "FILL_SEARCH_TERM",
--- a/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
+++ b/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
@@ -103,16 +103,17 @@ export class ASRouterUISurface extends R
     }
   }
 
   sendUserActionTelemetry(extraProps = {}) {
     const {message, bundle} = this.state;
     if (!message && !extraProps.message_id) {
       throw new Error(`You must provide a message_id for bundled messages`);
     }
+    // snippets_user_event, onboarding_user_event
     const eventType = `${message.provider || bundle.provider}_user_event`;
     ASRouterUtils.sendTelemetry({
       message_id: message.id || extraProps.message_id,
       source: extraProps.id,
       action: eventType,
       ...extraProps,
     });
   }
@@ -155,50 +156,67 @@ export class ASRouterUISurface extends R
     return options => ASRouterUtils.blockById(id, options);
   }
 
   onDismissById(id) {
     return () => ASRouterUtils.dismissById(id);
   }
 
   dismissBundle(bundle) {
-    return () => ASRouterUtils.dismissBundle(bundle);
+    return () => {
+      ASRouterUtils.dismissBundle(bundle);
+      this.sendUserActionTelemetry({
+        event: "DISMISS",
+        id: "onboarding-cards",
+        message_id: bundle.map(m => m.id).join(","),
+        // Passing the action because some bundles (Trailhead) don't have a provider set
+        action: "onboarding_user_event",
+      });
+    };
   }
 
   triggerOnboarding() {
     ASRouterUtils.sendMessage({type: "TRIGGER", data: {trigger: {id: "showOnboarding"}}});
   }
 
+  clearMessage(id) {
+    if (id === this.state.message.id) {
+      this.setState({message: {}});
+      // Remove any styles related to the RTAMO message
+      document.body.classList.remove("welcome", "hide-main", "amo");
+    }
+  }
+
   onMessageFromParent({data: action}) {
     switch (action.type) {
       case "SET_MESSAGE":
         this.setState({message: action.data});
         break;
       case "SET_BUNDLED_MESSAGES":
         this.setState({bundle: action.data});
         break;
       case "CLEAR_MESSAGE":
-        if (action.data.id === this.state.message.id) {
-          this.setState({message: {}});
-          // Remove any styles related to the RTAMO message
-          document.body.classList.remove("welcome", "hide-main", "amo");
-        }
+        this.clearMessage(action.data.id);
         break;
       case "CLEAR_PROVIDER":
         if (action.data.id === this.state.message.provider) {
           this.setState({message: {}});
         }
         break;
       case "CLEAR_BUNDLE":
         if (this.state.bundle.bundle) {
           this.setState({bundle: {}});
         }
         break;
       case "CLEAR_ALL":
         this.setState({message: {}, bundle: {}});
+        break;
+      case "AS_ROUTER_TARGETING_UPDATE":
+        action.data.forEach(id => this.clearMessage(id));
+        break;
     }
   }
 
   componentWillMount() {
     if (global.document) {
       // Add locale data for StartupOverlay because it uses react-intl
       addLocaleData(global.document.documentElement.lang);
     }
@@ -254,17 +272,17 @@ export class ASRouterUISurface extends R
 
   renderOnboarding() {
     if (this.state.bundle.template === "onboarding") {
       return (
         <OnboardingMessage
           {...this.state.bundle}
           UISurface="NEWTAB_OVERLAY"
           onAction={ASRouterUtils.executeAction}
-          onDoneButton={this.dismissBundle(this.state.bundle.bundle)}
+          onDismissBundle={this.dismissBundle(this.state.bundle.bundle)}
           sendUserActionTelemetry={this.sendUserActionTelemetry} />);
     }
     return null;
   }
 
   renderFirstRunOverlay() {
     const {message} = this.state;
     if (message.template === "fxa_overlay") {
@@ -278,33 +296,35 @@ export class ASRouterUISurface extends R
         </IntlProvider>
       );
     } else if (message.template === "return_to_amo_overlay") {
       global.document.body.classList.add("amo");
       return (
         <LocalizationProvider messages={generateMessages({"amo_html": message.content.text})}>
           <ReturnToAMO
             {...message}
+            UISurface="NEWTAB_OVERLAY"
             onReady={this.triggerOnboarding}
             onBlock={this.onDismissById(message.id)}
-            onAction={ASRouterUtils.executeAction} />
+            onAction={ASRouterUtils.executeAction}
+            sendUserActionTelemetry={this.sendUserActionTelemetry} />
         </LocalizationProvider>
       );
     }
     return null;
   }
 
   renderTrailhead() {
     const {message} = this.state;
     if (message.template === "trailhead") {
       return (<Trailhead
         document={this.props.document}
         message={message}
         onAction={ASRouterUtils.executeAction}
-        onDoneButton={this.dismissBundle(this.state.bundle.bundle)}
+        onDismissBundle={this.dismissBundle(this.state.message.bundle)}
         sendUserActionTelemetry={this.sendUserActionTelemetry}
         dispatch={this.props.dispatch}
         fxaEndpoint={this.props.fxaEndpoint} />);
     }
     return null;
   }
 
   renderPreviewBanner() {
--- a/browser/components/newtab/content-src/asrouter/components/ModalOverlay/ModalOverlay.jsx
+++ b/browser/components/newtab/content-src/asrouter/components/ModalOverlay/ModalOverlay.jsx
@@ -37,18 +37,18 @@ export class ModalOverlayWrapper extends
 }
 
 ModalOverlayWrapper.defaultProps = {document: global.document};
 
 export class ModalOverlay extends React.PureComponent {
   render() {
     const {title, button_label} = this.props;
     return (
-      <ModalOverlayWrapper onClose={this.props.onDoneButton}>
+      <ModalOverlayWrapper onClose={this.props.onDismissBundle}>
         <h2> {title} </h2>
         {this.props.children}
         <div className="footer">
           <button className="button primary modalButton"
-            onClick={this.props.onDoneButton}> {button_label} </button>
+            onClick={this.props.onDismissBundle}> {button_label} </button>
         </div>
       </ModalOverlayWrapper>);
   }
 }
--- a/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
@@ -5,25 +5,37 @@ export class ReturnToAMO extends React.P
   constructor(props) {
     super(props);
     this.onClickAddExtension = this.onClickAddExtension.bind(this);
     this.onBlockButton = this.onBlockButton.bind(this);
   }
 
   componentDidMount() {
     this.props.onReady();
+    this.props.sendUserActionTelemetry({
+      event: "IMPRESSION",
+      id: this.props.UISurface,
+    });
   }
 
   onClickAddExtension() {
     this.props.onAction(this.props.content.primary_button.action);
+    this.props.sendUserActionTelemetry({
+      event: "INSTALL",
+      id: this.props.UISurface,
+    });
   }
 
   onBlockButton() {
     this.props.onBlock();
     document.body.classList.remove("welcome", "hide-main", "amo");
+    this.props.sendUserActionTelemetry({
+      event: "BLOCK",
+      id: this.props.UISurface,
+    });
   }
 
   renderText() {
     const customElement = <img src={this.props.content.addon_icon} width="20px" height="20px" />;
     return (<RichText
       customElements={{icon: customElement}}
       amo_html={this.props.content.text}
       localization_id="amo_html" />);
--- a/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
@@ -114,17 +114,27 @@ export class _Trailhead extends React.Pu
     const {dialog} = this;
     if (event.relatedTarget &&
         !(dialog.compareDocumentPosition(event.relatedTarget) &
           dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
       dialog.querySelector(FOCUSABLE_SELECTOR).focus();
     }
   }
 
-  onSubmit() {
+  onSubmit(event) {
+    // Dynamically require the email on submission so screen readers don't read
+    // out it's always required because there's also ways to skip the modal
+    const {email} = event.target.elements;
+    if (!email.value.length) {
+      email.required = true;
+      email.checkValidity();
+      event.preventDefault();
+      return;
+    }
+
     this.props.dispatch(ac.UserEvent({event: "SUBMIT_EMAIL", ...this._getFormInfo()}));
 
     global.addEventListener("visibilitychange", this.closeModal);
   }
 
   closeModal(ev) {
     global.removeEventListener("visibilitychange", this.closeModal);
     this.props.document.body.classList.remove("welcome");
@@ -132,16 +142,19 @@ export class _Trailhead extends React.Pu
     this.setState({isModalOpen: false});
     this.revealCards();
 
     // If closeModal() was triggered by a visibilitychange event, the user actually
     // submitted the email form so we don't send a SKIPPED_SIGNIN ping.
     if (!ev || ev.type !== "visibilitychange") {
       this.props.dispatch(ac.UserEvent({event: "SKIPPED_SIGNIN", ...this._getFormInfo()}));
     }
+
+    // Bug 1190882 - Focus in a disappearing dialog confuses screen readers
+    this.props.document.activeElement.blur();
   }
 
   /**
    * Report to telemetry additional information about the form submission.
    */
   _getFormInfo() {
     const value = {has_flow_params: this.state.flowId.length > 0};
     return {value};
@@ -152,16 +165,17 @@ export class _Trailhead extends React.Pu
     error.classList.add("active");
     e.target.classList.add("invalid");
     e.preventDefault(); // Override built-in form validation popup
     e.target.focus();
   }
 
   hideCardPanel() {
     this.setState({showCardPanel: false});
+    this.props.onDismissBundle();
   }
 
   revealCards() {
     this.setState({showCards: true});
   }
 
   getStringValue(str) {
     if (str.property_id) {
@@ -251,17 +265,16 @@ export class _Trailhead extends React.Pu
             <input name="flow_begin_time" type="hidden" value={this.state.flowBeginTime} />
             <input name="style" type="hidden" value="trailhead" />
             <p data-l10n-id="onboarding-join-form-email-error" className="error" />
             <input
               data-l10n-id={content.form.email.string_id}
               placeholder={this.getStringValue(content.form.email)}
               name="email"
               type="email"
-              required="required"
               onInvalid={this.onInputInvalid}
               onChange={this.onInputChange} />
             <p className="trailheadTerms" data-l10n-id="onboarding-join-form-legal">
               <a data-l10n-name="terms" target="_blank" rel="noopener noreferrer"
                 href={this.addUtmParams("https://accounts.firefox.com/legal/terms")} />
               <a data-l10n-name="privacy" target="_blank" rel="noopener noreferrer"
                 href={this.addUtmParams("https://accounts.firefox.com/legal/privacy")} />
             </p>
--- a/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.jsx
+++ b/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.jsx
@@ -20,17 +20,16 @@ function relativeTime(timestamp) {
   } else if (minutes === 1) {
     return "1 minute ago";
   } else if (minutes < 600) {
     return `${minutes} minutes ago`;
   }
   return new Date(timestamp).toLocaleString();
 }
 
-const OPT_OUT_PREF = "discoverystream.optOut.0";
 const LAYOUT_VARIANTS = {
   "basic": "Basic default layout (on by default in nightly)",
   "dev-test-all": "A little bit of everything. Good layout for testing all components",
   "dev-test-feeds": "Stress testing for slow feeds",
 };
 
 export class ToggleStoryButton extends React.PureComponent {
   constructor(props) {
@@ -55,20 +54,16 @@ export class DiscoveryStreamAdmin extend
     this.onEnableToggle = this.onEnableToggle.bind(this);
     this.changeEndpointVariant = this.changeEndpointVariant.bind(this);
     this.onStoryToggle = this.onStoryToggle.bind(this);
     this.state = {
       toggledStories: {},
     };
   }
 
-  get isOptedOut() {
-    return this.props.otherPrefs[OPT_OUT_PREF];
-  }
-
   setConfigValue(name, value) {
     this.props.dispatch(ac.OnlyToMain({type: at.DISCOVERY_STREAM_CONFIG_SET_VALUE, data: {name, value}}));
   }
 
   onEnableToggle(event) {
     this.setConfigValue("enabled", event.target.checked);
   }
 
@@ -195,23 +190,20 @@ export class DiscoveryStreamAdmin extend
           <td className="min">Data last fetched</td>
           <td>{relativeTime(feeds.data[feed.url] ? feeds.data[feed.url].lastUpdated : null) || "(no data)"}</td>
         </Row>
       </React.Fragment>
     );
   }
 
   render() {
-    const {isOptedOut} = this;
     const {config, lastUpdated, layout} = this.props.state;
     return (<div>
 
-      <div className="dsEnabled"><input type="checkbox" checked={config.enabled} onChange={this.onEnableToggle} /> enabled
-        {isOptedOut ? (<span className="optOutNote">(Note: User has opted-out. Check this box to reset)</span>) : ""}</div>
-
+      <div className="dsEnabled"><input type="checkbox" checked={config.enabled} onChange={this.onEnableToggle} /> enabled </div>
       <h3>Endpoint variant</h3>
       <p>You can also change this manually by changing this pref: <code>browser.newtabpage.activity-stream.discoverystream.config</code></p>
       <table style={config.enabled ? null : {opacity: 0.5}}><tbody>
         {Object.keys(LAYOUT_VARIANTS).map(id => (<Row key={id}>
           <td className="min"><input type="radio" value={id} checked={this.isCurrentVariant(id)} onChange={this.changeEndpointVariant} /></td>
           <td className="min">{id}</td>
           <td>{LAYOUT_VARIANTS[id]}</td>
         </Row>))}
--- a/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.scss
+++ b/browser/components/newtab/content-src/components/ASRouterAdmin/ASRouterAdmin.scss
@@ -214,14 +214,9 @@
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid $border-color;
   }
 
   .ds-component {
     margin-bottom: 20px;
   }
-
-  .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px;
-  }
 }
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -1758,19 +1758,16 @@ main {
       text-decoration: underline; }
   .asrouter-admin .dsEnabled {
     padding: 10px;
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid var(--newtab-border-secondary-color); }
   .asrouter-admin .ds-component {
     margin-bottom: 20px; }
-  .asrouter-admin .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px; }
 
 .pocket-logged-in-cta {
   font-size: 13px;
   margin-inline-end: 20px;
   display: flex;
   align-items: flex-start; }
   .pocket-logged-in-cta .pocket-cta-button {
     white-space: nowrap;
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -1761,19 +1761,16 @@ main {
       text-decoration: underline; }
   .asrouter-admin .dsEnabled {
     padding: 10px;
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid var(--newtab-border-secondary-color); }
   .asrouter-admin .ds-component {
     margin-bottom: 20px; }
-  .asrouter-admin .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px; }
 
 .pocket-logged-in-cta {
   font-size: 13px;
   margin-inline-end: 20px;
   display: flex;
   align-items: flex-start; }
   .pocket-logged-in-cta .pocket-cta-button {
     white-space: nowrap;
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -1758,19 +1758,16 @@ main {
       text-decoration: underline; }
   .asrouter-admin .dsEnabled {
     padding: 10px;
     font-size: 16px;
     margin-bottom: 20px;
     border: 1px solid var(--newtab-border-secondary-color); }
   .asrouter-admin .ds-component {
     margin-bottom: 20px; }
-  .asrouter-admin .optOutNote {
-    font-size: 12px;
-    margin-inline-start: 4px; }
 
 .pocket-logged-in-cta {
   font-size: 13px;
   margin-inline-end: 20px;
   display: flex;
   align-items: flex-start; }
   .pocket-logged-in-cta .pocket-cta-button {
     white-space: nowrap;
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -192,17 +192,17 @@ const globalImportContext = typeof Windo
 
 // Create an object that avoids accidental differing key/value pairs:
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 const actionTypes = {};
 
-for (const type of ["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_FEED_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_OPT_OUT", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_SEARCH", "INIT", "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_LINK_DELETED_OR_ARCHIVED", "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", "TRAILHEAD_ENROLL_EVENT", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
+for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TARGETING_UPDATE", "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_FEED_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_SEARCH", "INIT", "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_LINK_DELETED_OR_ARCHIVED", "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", "TRAILHEAD_ENROLL_EVENT", "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", "PIN_CURRENT_TAB"]) {
@@ -836,17 +836,16 @@ function relativeTime(timestamp) {
     return "1 minute ago";
   } else if (minutes < 600) {
     return `${minutes} minutes ago`;
   }
 
   return new Date(timestamp).toLocaleString();
 }
 
-const OPT_OUT_PREF = "discoverystream.optOut.0";
 const LAYOUT_VARIANTS = {
   "basic": "Basic default layout (on by default in nightly)",
   "dev-test-all": "A little bit of everything. Good layout for testing all components",
   "dev-test-feeds": "Stress testing for slow feeds"
 };
 class ToggleStoryButton extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent {
   constructor(props) {
     super(props);
@@ -870,20 +869,16 @@ class DiscoveryStreamAdmin extends react
     this.onEnableToggle = this.onEnableToggle.bind(this);
     this.changeEndpointVariant = this.changeEndpointVariant.bind(this);
     this.onStoryToggle = this.onStoryToggle.bind(this);
     this.state = {
       toggledStories: {}
     };
   }
 
-  get isOptedOut() {
-    return this.props.otherPrefs[OPT_OUT_PREF];
-  }
-
   setConfigValue(name, value) {
     this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].OnlyToMain({
       type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].DISCOVERY_STREAM_CONFIG_SET_VALUE,
       data: {
         name,
         value
       }
     }));
@@ -991,32 +986,27 @@ class DiscoveryStreamAdmin extends react
       className: "min"
     }, "Feed url"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", null, feed.url)), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(Row, null, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", {
       className: "min"
     }, "Data last fetched"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", null, relativeTime(feeds.data[feed.url] ? feeds.data[feed.url].lastUpdated : null) || "(no data)")));
   }
 
   render() {
     const {
-      isOptedOut
-    } = this;
-    const {
       config,
       lastUpdated,
       layout
     } = this.props.state;
     return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", null, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
       className: "dsEnabled"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
       type: "checkbox",
       checked: config.enabled,
       onChange: this.onEnableToggle
-    }), " enabled", isOptedOut ? react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("span", {
-      className: "optOutNote"
-    }, "(Note: User has opted-out. Check this box to reset)") : ""), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h3", null, "Endpoint variant"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", null, "You can also change this manually by changing this pref: ", react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("code", null, "browser.newtabpage.activity-stream.discoverystream.config")), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("table", {
+    }), " enabled "), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h3", null, "Endpoint variant"), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", null, "You can also change this manually by changing this pref: ", react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("code", null, "browser.newtabpage.activity-stream.discoverystream.config")), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("table", {
       style: config.enabled ? null : {
         opacity: 0.5
       }
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("tbody", null, Object.keys(LAYOUT_VARIANTS).map(id => react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(Row, {
       key: id
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("td", {
       className: "min"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
@@ -1941,17 +1931,18 @@ class ASRouterUISurface extends react__W
   sendUserActionTelemetry(extraProps = {}) {
     const {
       message,
       bundle
     } = this.state;
 
     if (!message && !extraProps.message_id) {
       throw new Error(`You must provide a message_id for bundled messages`);
-    }
+    } // snippets_user_event, onboarding_user_event
+
 
     const eventType = `${message.provider || bundle.provider}_user_event`;
     ASRouterUtils.sendTelemetry({
       message_id: message.id || extraProps.message_id,
       source: extraProps.id,
       action: eventType,
       ...extraProps
     });
@@ -2009,30 +2000,49 @@ class ASRouterUISurface extends react__W
     return options => ASRouterUtils.blockById(id, options);
   }
 
   onDismissById(id) {
     return () => ASRouterUtils.dismissById(id);
   }
 
   dismissBundle(bundle) {
-    return () => ASRouterUtils.dismissBundle(bundle);
+    return () => {
+      ASRouterUtils.dismissBundle(bundle);
+      this.sendUserActionTelemetry({
+        event: "DISMISS",
+        id: "onboarding-cards",
+        message_id: bundle.map(m => m.id).join(","),
+        // Passing the action because some bundles (Trailhead) don't have a provider set
+        action: "onboarding_user_event"
+      });
+    };
   }
 
   triggerOnboarding() {
     ASRouterUtils.sendMessage({
       type: "TRIGGER",
       data: {
         trigger: {
           id: "showOnboarding"
         }
       }
     });
   }
 
+  clearMessage(id) {
+    if (id === this.state.message.id) {
+      this.setState({
+        message: {}
+      }); // Remove any styles related to the RTAMO message
+
+      document.body.classList.remove("welcome", "hide-main", "amo");
+    }
+  }
+
   onMessageFromParent({
     data: action
   }) {
     switch (action.type) {
       case "SET_MESSAGE":
         this.setState({
           message: action.data
         });
@@ -2040,24 +2050,17 @@ class ASRouterUISurface extends react__W
 
       case "SET_BUNDLED_MESSAGES":
         this.setState({
           bundle: action.data
         });
         break;
 
       case "CLEAR_MESSAGE":
-        if (action.data.id === this.state.message.id) {
-          this.setState({
-            message: {}
-          }); // Remove any styles related to the RTAMO message
-
-          document.body.classList.remove("welcome", "hide-main", "amo");
-        }
-
+        this.clearMessage(action.data.id);
         break;
 
       case "CLEAR_PROVIDER":
         if (action.data.id === this.state.message.provider) {
           this.setState({
             message: {}
           });
         }
@@ -2073,16 +2076,21 @@ class ASRouterUISurface extends react__W
 
         break;
 
       case "CLEAR_ALL":
         this.setState({
           message: {},
           bundle: {}
         });
+        break;
+
+      case "AS_ROUTER_TARGETING_UPDATE":
+        action.data.forEach(id => this.clearMessage(id));
+        break;
     }
   }
 
   componentWillMount() {
     if (global.document) {
       // Add locale data for StartupOverlay because it uses react-intl
       Object(react_intl__WEBPACK_IMPORTED_MODULE_0__["addLocaleData"])(global.document.documentElement.lang);
     }
@@ -2150,17 +2158,17 @@ class ASRouterUISurface extends react__W
     }))));
   }
 
   renderOnboarding() {
     if (this.state.bundle.template === "onboarding") {
       return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_7__["OnboardingMessage"], _extends({}, this.state.bundle, {
         UISurface: "NEWTAB_OVERLAY",
         onAction: ASRouterUtils.executeAction,
-        onDoneButton: this.dismissBundle(this.state.bundle.bundle),
+        onDismissBundle: this.dismissBundle(this.state.bundle.bundle),
         sendUserActionTelemetry: this.sendUserActionTelemetry
       }));
     }
 
     return null;
   }
 
   renderFirstRunOverlay() {
@@ -2180,36 +2188,38 @@ class ASRouterUISurface extends react__W
       }));
     } else if (message.template === "return_to_amo_overlay") {
       global.document.body.classList.add("amo");
       return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(fluent_react__WEBPACK_IMPORTED_MODULE_5__["LocalizationProvider"], {
         messages: Object(_rich_text_strings__WEBPACK_IMPORTED_MODULE_3__["generateMessages"])({
           "amo_html": message.content.text
         })
       }, react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(_templates_ReturnToAMO_ReturnToAMO__WEBPACK_IMPORTED_MODULE_10__["ReturnToAMO"], _extends({}, message, {
+        UISurface: "NEWTAB_OVERLAY",
         onReady: this.triggerOnboarding,
         onBlock: this.onDismissById(message.id),
-        onAction: ASRouterUtils.executeAction
+        onAction: ASRouterUtils.executeAction,
+        sendUserActionTelemetry: this.sendUserActionTelemetry
       })));
     }
 
     return null;
   }
 
   renderTrailhead() {
     const {
       message
     } = this.state;
 
     if (message.template === "trailhead") {
       return react__WEBPACK_IMPORTED_MODULE_8___default.a.createElement(_templates_Trailhead_Trailhead__WEBPACK_IMPORTED_MODULE_13__["Trailhead"], {
         document: this.props.document,
         message: message,
         onAction: ASRouterUtils.executeAction,
-        onDoneButton: this.dismissBundle(this.state.bundle.bundle),
+        onDismissBundle: this.dismissBundle(this.state.message.bundle),
         sendUserActionTelemetry: this.sendUserActionTelemetry,
         dispatch: this.props.dispatch,
         fxaEndpoint: this.props.fxaEndpoint
       });
     }
 
     return null;
   }
@@ -2743,22 +2753,22 @@ ModalOverlayWrapper.defaultProps = {
 };
 class ModalOverlay extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
   render() {
     const {
       title,
       button_label
     } = this.props;
     return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(ModalOverlayWrapper, {
-      onClose: this.props.onDoneButton
+      onClose: this.props.onDismissBundle
     }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", null, " ", title, " "), this.props.children, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
       className: "footer"
     }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
       className: "button primary modalButton",
-      onClick: this.props.onDoneButton
+      onClick: this.props.onDismissBundle
     }, " ", button_label, " ")));
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
 /* 16 */
@@ -2782,25 +2792,37 @@ class ReturnToAMO extends react__WEBPACK
   constructor(props) {
     super(props);
     this.onClickAddExtension = this.onClickAddExtension.bind(this);
     this.onBlockButton = this.onBlockButton.bind(this);
   }
 
   componentDidMount() {
     this.props.onReady();
+    this.props.sendUserActionTelemetry({
+      event: "IMPRESSION",
+      id: this.props.UISurface
+    });
   }
 
   onClickAddExtension() {
     this.props.onAction(this.props.content.primary_button.action);
+    this.props.sendUserActionTelemetry({
+      event: "INSTALL",
+      id: this.props.UISurface
+    });
   }
 
   onBlockButton() {
     this.props.onBlock();
     document.body.classList.remove("welcome", "hide-main", "amo");
+    this.props.sendUserActionTelemetry({
+      event: "BLOCK",
+      id: this.props.UISurface
+    });
   }
 
   renderText() {
     const customElement = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {
       src: this.props.content.addon_icon,
       width: "20px",
       height: "20px"
     });
@@ -3415,17 +3437,30 @@ class _Trailhead extends react__WEBPACK_
       dialog
     } = this;
 
     if (event.relatedTarget && !(dialog.compareDocumentPosition(event.relatedTarget) & dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
       dialog.querySelector(FOCUSABLE_SELECTOR).focus();
     }
   }
 
-  onSubmit() {
+  onSubmit(event) {
+    // Dynamically require the email on submission so screen readers don't read
+    // out it's always required because there's also ways to skip the modal
+    const {
+      email
+    } = event.target.elements;
+
+    if (!email.value.length) {
+      email.required = true;
+      email.checkValidity();
+      event.preventDefault();
+      return;
+    }
+
     this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
       event: "SUBMIT_EMAIL",
       ...this._getFormInfo()
     }));
     global.addEventListener("visibilitychange", this.closeModal);
   }
 
   closeModal(ev) {
@@ -3438,17 +3473,20 @@ class _Trailhead extends react__WEBPACK_
     this.revealCards(); // If closeModal() was triggered by a visibilitychange event, the user actually
     // submitted the email form so we don't send a SKIPPED_SIGNIN ping.
 
     if (!ev || ev.type !== "visibilitychange") {
       this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
         event: "SKIPPED_SIGNIN",
         ...this._getFormInfo()
       }));
-    }
+    } // Bug 1190882 - Focus in a disappearing dialog confuses screen readers
+
+
+    this.props.document.activeElement.blur();
   }
   /**
    * Report to telemetry additional information about the form submission.
    */
 
 
   _getFormInfo() {
     const value = {
@@ -3467,16 +3505,17 @@ class _Trailhead extends react__WEBPACK_
 
     e.target.focus();
   }
 
   hideCardPanel() {
     this.setState({
       showCardPanel: false
     });
+    this.props.onDismissBundle();
   }
 
   revealCards() {
     this.setState({
       showCards: true
     });
   }
 
@@ -3639,17 +3678,16 @@ class _Trailhead extends react__WEBPACK_
     }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
       "data-l10n-id": "onboarding-join-form-email-error",
       className: "error"
     }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", {
       "data-l10n-id": content.form.email.string_id,
       placeholder: this.getStringValue(content.form.email),
       name: "email",
       type: "email",
-      required: "required",
       onInvalid: this.onInputInvalid,
       onChange: this.onInputChange
     }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
       className: "trailheadTerms",
       "data-l10n-id": "onboarding-join-form-legal"
     }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("a", {
       "data-l10n-name": "terms",
       target: "_blank",
--- a/browser/components/newtab/docs/v2-system-addon/data_events.md
+++ b/browser/components/newtab/docs/v2-system-addon/data_events.md
@@ -980,17 +980,17 @@ This reports the user's interaction with
 {
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "action": "onboarding_user_event",
   "addon_version": "20180710100040",
   "impression_id": "n/a",
   "locale": "en-US",
   "source": "ONBOARDING",
   "message_id": "onboarding_message_1",
-  "event": "CLICK_BUTTION"
+  "event": ["IMPRESSION" | "CLICK_BUTTION" | "INSTALL" | "BLOCK"]
 }
 ```
 
 #### CFR interaction pings for all the prerelease channels and shield experiment
 ```js
 {
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "action": "cfr_user_event",
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -20,16 +20,18 @@ const {ASRouterActions: ra, actionTypes:
 const {CFRMessageProvider} = ChromeUtils.import("resource://activity-stream/lib/CFRMessageProvider.jsm");
 const {OnboardingMessageProvider} = ChromeUtils.import("resource://activity-stream/lib/OnboardingMessageProvider.jsm");
 const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js");
 const {CFRPageActions} = ChromeUtils.import("resource://activity-stream/lib/CFRPageActions.jsm");
 const {AttributionCode} = ChromeUtils.import("resource:///modules/AttributionCode.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ASRouterPreferences",
   "resource://activity-stream/lib/ASRouterPreferences.jsm");
+ChromeUtils.defineModuleGetter(this, "TARGETING_PREFERENCES",
+  "resource://activity-stream/lib/ASRouterPreferences.jsm");
 ChromeUtils.defineModuleGetter(this, "ASRouterTargeting",
   "resource://activity-stream/lib/ASRouterTargeting.jsm");
 ChromeUtils.defineModuleGetter(this, "QueryCache",
   "resource://activity-stream/lib/ASRouterTargeting.jsm");
 ChromeUtils.defineModuleGetter(this, "ASRouterTriggerListeners",
   "resource://activity-stream/lib/ASRouterTriggerListeners.jsm");
 ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment",
   "resource://gre/modules/TelemetryEnvironment.jsm");
@@ -400,21 +402,36 @@ class _ASRouter {
     this._localProviders = localProviders;
     this.onMessage = this.onMessage.bind(this);
     this.handleMessageRequest = this.handleMessageRequest.bind(this);
     this.addImpression = this.addImpression.bind(this);
     this._handleTargetingError = this._handleTargetingError.bind(this);
     this.onPrefChange = this.onPrefChange.bind(this);
   }
 
-  // Update message providers and fetch new messages on pref change
-  async onPrefChange() {
-    this._loadLocalProviders();
-    this._updateMessageProviders();
-    await this.loadMessagesFromAllProviders();
+  async onPrefChange(prefName) {
+    if (TARGETING_PREFERENCES.includes(prefName)) {
+      // Notify all tabs of messages that have become invalid after pref change
+      const invalidMessages = [];
+      for (const msg of this._getUnblockedMessages()) {
+        if (!msg.targeting) {
+          continue;
+        }
+        const isMatch = await ASRouterTargeting.isMatch(msg.targeting);
+        if (!isMatch) {
+          invalidMessages.push(msg.id);
+        }
+      }
+      this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: at.AS_ROUTER_TARGETING_UPDATE, data: invalidMessages});
+    } else {
+      // Update message providers and fetch new messages on pref change
+      this._loadLocalProviders();
+      this._updateMessageProviders();
+      await this.loadMessagesFromAllProviders();
+    }
   }
 
   // Replace all frequency time period aliases with their millisecond values
   // This allows us to avoid accounting for special cases later on
   normalizeItemFrequency({frequency}) {
     if (frequency && frequency.custom) {
       for (const setting of frequency.custom) {
         if (setting.period === "daily") {
--- a/browser/components/newtab/lib/ASRouterPreferences.jsm
+++ b/browser/components/newtab/lib/ASRouterPreferences.jsm
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const PROVIDER_PREF_BRANCH = "browser.newtabpage.activity-stream.asrouter.providers.";
 const DEVTOOLS_PREF = "browser.newtabpage.activity-stream.asrouter.devtoolsEnabled";
+const FXA_USERNAME_PREF = "services.sync.username";
 
 const DEFAULT_STATE = {
   _initialized: false,
   _providers: null,
   _providerPrefBranch: PROVIDER_PREF_BRANCH,
   _devtoolsEnabled: null,
   _devtoolsPref: DEVTOOLS_PREF,
 };
@@ -22,16 +23,20 @@ const MIGRATE_PREFS = [
 ];
 
 const USER_PREFERENCES = {
   snippets: "browser.newtabpage.activity-stream.feeds.snippets",
   cfrAddons: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons",
   cfrFeatures: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
 };
 
+// Preferences that influence targeting attributes. When these change we need
+// to re-evaluate if the message targeting still matches
+const TARGETING_PREFERENCES = [FXA_USERNAME_PREF];
+
 const TEST_PROVIDERS = [{
   id: "snippets_local_testing",
   type: "local",
   localProvider: "SnippetsTestMessageProvider",
   enabled: true,
 }, {
   id: "panel_local_testing",
   type: "local",
@@ -164,29 +169,36 @@ class _ASRouterPreferences {
       return;
     }
     this._migratePrefs();
     Services.prefs.addObserver(this._providerPrefBranch, this);
     Services.prefs.addObserver(this._devtoolsPref, this);
     for (const id of Object.keys(USER_PREFERENCES)) {
       Services.prefs.addObserver(USER_PREFERENCES[id], this);
     }
+    for (const targetingPref of TARGETING_PREFERENCES) {
+      Services.prefs.addObserver(targetingPref, this);
+    }
     this._initialized = true;
   }
 
   uninit() {
     if (this._initialized) {
       Services.prefs.removeObserver(this._providerPrefBranch, this);
       Services.prefs.removeObserver(this._devtoolsPref, this);
       for (const id of Object.keys(USER_PREFERENCES)) {
         Services.prefs.removeObserver(USER_PREFERENCES[id], this);
       }
+      for (const targetingPref of TARGETING_PREFERENCES) {
+        Services.prefs.removeObserver(targetingPref, this);
+      }
     }
     Object.assign(this, DEFAULT_STATE);
     this._callbacks.clear();
   }
 }
 this._ASRouterPreferences = _ASRouterPreferences;
 
 this.ASRouterPreferences = new _ASRouterPreferences();
 this.TEST_PROVIDERS = TEST_PROVIDERS;
+this.TARGETING_PREFERENCES = TARGETING_PREFERENCES;
 
-const EXPORTED_SYMBOLS = ["_ASRouterPreferences", "ASRouterPreferences", "TEST_PROVIDERS"];
+const EXPORTED_SYMBOLS = ["_ASRouterPreferences", "ASRouterPreferences", "TEST_PROVIDERS", "TARGETING_PREFERENCES"];
--- a/browser/components/newtab/lib/AboutPreferences.jsm
+++ b/browser/components/newtab/lib/AboutPreferences.jsm
@@ -101,17 +101,16 @@ this.AboutPreferences = class AboutPrefe
     let sectionsCopy = JSON.parse(JSON.stringify(sections));
     sectionsCopy.forEach(obj => {
       if (obj.id === "highlights") {
         obj.shouldHidePref = true;
       }
 
       if (obj.id === "topstories") {
         obj.rowsPref = "";
-        obj.pref.descString = {id: "prefs_content_discovery_description"};
       }
     });
     return sectionsCopy;
   }
 
   async observe(window) {
     const discoveryStreamConfig = this.store.getState().DiscoveryStream.config;
     let sections = this.store.getState().Sections;
--- a/browser/components/newtab/lib/ActivityStream.jsm
+++ b/browser/components/newtab/lib/ActivityStream.jsm
@@ -241,20 +241,16 @@ const PREFS_CONFIG = new Map([
         layout_endpoint: "https://getpocket.cdn.mozilla.net/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic",
       });
     },
   }],
   ["discoverystream.endpoints", {
     title: "Endpoint prefixes (comma-separated) that are allowed to be requested",
     value: "https://getpocket.cdn.mozilla.net/",
   }],
-  ["discoverystream.optOut.0", {
-    title: "Opt out of new layout v0",
-    value: false,
-  }],
   ["discoverystream.spoc.impressions", {
     title: "Track spoc impressions",
     skipBroadcast: true,
     value: "{}",
   }],
   ["discoverystream.rec.impressions", {
     title: "Track rec impressions",
     skipBroadcast: true,
@@ -297,17 +293,17 @@ const FEEDS_DATA = [
   {
     name: "section.highlights",
     factory: () => new HighlightsFeed(),
     title: "Fetches content recommendations from places db",
     value: true,
   },
   {
     name: "section.topstories",
-    factory: () => new TopStoriesFeed(),
+    factory: () => new TopStoriesFeed(PREFS_CONFIG.get("discoverystream.config")),
     title: "Fetches content recommendations from a configurable content provider",
     // Dynamically determine if Pocket should be shown for a geo / locale
     getValue: ({geo, locale}) => {
       const locales = ({
         "US": ["en-CA", "en-GB", "en-US", "en-ZA"],
         "CA": ["en-CA", "en-GB", "en-US", "en-ZA"],
         "DE": ["de", "de-DE", "de-AT", "de-CH"],
       })[geo];
--- a/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
+++ b/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
@@ -21,17 +21,16 @@ const COMPONENT_FEEDS_UPDATE_TIME = 30 *
 const SPOCS_FEEDS_UPDATE_TIME = 30 * 60 * 1000; // 30 minutes
 const DEFAULT_RECS_EXPIRE_TIME = 60 * 60 * 1000; // 1 hour
 const MIN_DOMAIN_AFFINITIES_UPDATE_TIME = 12 * 60 * 60 * 1000; // 12 hours
 const MAX_LIFETIME_CAP = 500; // Guard against misconfiguration on the server
 const DEFAULT_MAX_HISTORY_QUERY_RESULTS = 1000;
 const FETCH_TIMEOUT = 45 * 1000;
 const PREF_CONFIG = "discoverystream.config";
 const PREF_ENDPOINTS = "discoverystream.endpoints";
-const PREF_OPT_OUT = "discoverystream.optOut.0";
 const PREF_SHOW_SPONSORED = "showSponsored";
 const PREF_SPOC_IMPRESSIONS = "discoverystream.spoc.impressions";
 const PREF_REC_IMPRESSIONS = "discoverystream.rec.impressions";
 
 let defaultLayoutResp;
 
 this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
   constructor() {
@@ -85,20 +84,16 @@ this.DiscoveryStreamFeed = class Discove
       this._prefCache.config = JSON.parse(this.store.getState().Prefs.values[PREF_CONFIG]);
       const layoutUrl = this._prefCache.config.layout_endpoint;
 
       const apiKeyPref = this._prefCache.config.api_key_pref;
       if (layoutUrl && apiKeyPref) {
         const apiKey = Services.prefs.getCharPref(apiKeyPref, "");
         this._prefCache.config.layout_endpoint = this.finalLayoutEndpoint(layoutUrl, apiKey);
       }
-
-      // Modify the cached config with the user set opt-out for other consumers
-      this._prefCache.config.enabled = this._prefCache.config.enabled &&
-        !this.store.getState().Prefs.values[PREF_OPT_OUT];
     } catch (e) {
       // istanbul ignore next
       this._prefCache.config = {};
       // istanbul ignore next
       Cu.reportError(`Could not parse preference. Try resetting ${PREF_CONFIG} in about:config. ${e}`);
     }
     return this._prefCache.config;
   }
@@ -911,35 +906,27 @@ this.DiscoveryStreamFeed = class Discove
         break;
       case at.SYSTEM_TICK:
         // Only refresh if we loaded once in .enable()
         if (this.config.enabled && this.loaded && await this.checkIfAnyCacheExpired()) {
           await this.refreshAll({updateOpenTabs: false});
         }
         break;
       case at.DISCOVERY_STREAM_CONFIG_SET_VALUE:
-        // Disable opt-out if we're explicitly trying to enable
-        if (action.data.name === "enabled" && action.data.value) {
-          this.store.dispatch(ac.SetPref(PREF_OPT_OUT, false));
-        }
-
         // Use the original string pref to then set a value instead of
         // this.config which has some modifications
         this.store.dispatch(ac.SetPref(PREF_CONFIG, JSON.stringify({
           ...JSON.parse(this.store.getState().Prefs.values[PREF_CONFIG]),
           [action.data.name]: action.data.value,
         })));
         break;
       case at.DISCOVERY_STREAM_CONFIG_CHANGE:
         // When the config pref changes, load or unload data as needed.
         await this.onPrefChange();
         break;
-      case at.DISCOVERY_STREAM_OPT_OUT:
-        this.store.dispatch(ac.SetPref(PREF_OPT_OUT, true));
-        break;
       case at.DISCOVERY_STREAM_IMPRESSION_STATS:
         if (action.data.tiles && action.data.tiles[0] && action.data.tiles[0].id) {
           this.recordTopRecImpressions(action.data.tiles[0].id);
         }
         break;
       case at.DISCOVERY_STREAM_SPOC_IMPRESSION:
         if (this.showSpocs) {
           this.recordCampaignImpression(action.data.campaignId);
@@ -976,17 +963,16 @@ this.DiscoveryStreamFeed = class Discove
         break;
       case at.UNINIT:
         // When this feed is shutting down:
         this.uninitPrefs();
         break;
       case at.PREF_CHANGED:
         switch (action.data.name) {
           case PREF_CONFIG:
-          case PREF_OPT_OUT:
             // Clear the cached config and broadcast the newly computed value
             this._prefCache.config = null;
             this.store.dispatch(ac.BroadcastToContent({
               type: at.DISCOVERY_STREAM_CONFIG_CHANGE,
               data: this.config,
             }));
             break;
           // Check if spocs was disabled. Remove them if they were.
--- a/browser/components/newtab/lib/TopStoriesFeed.jsm
+++ b/browser/components/newtab/lib/TopStoriesFeed.jsm
@@ -25,27 +25,43 @@ const STORIES_NOW_THRESHOLD = 24 * 60 * 
 const MIN_DOMAIN_AFFINITIES_UPDATE_TIME = 12 * 60 * 60 * 1000; // 12 hours
 const DEFAULT_RECS_EXPIRE_TIME = 60 * 60 * 1000; // 1 hour
 const SECTION_ID = "topstories";
 const IMPRESSION_SOURCE = "TOP_STORIES";
 const SPOC_IMPRESSION_TRACKING_PREF = "feeds.section.topstories.spoc.impressions";
 const REC_IMPRESSION_TRACKING_PREF = "feeds.section.topstories.rec.impressions";
 const OPTIONS_PREF = "feeds.section.topstories.options";
 const MAX_LIFETIME_CAP = 500; // Guard against misconfiguration on the server
+const DISCOVERY_STREAM_PREF = "discoverystream.config";
 
 this.TopStoriesFeed = class TopStoriesFeed {
-  constructor() {
+  constructor(ds) {
+    // Use discoverystream config pref default values for fast path and
+    // if needed lazy load activity stream top stories feed based on
+    // actual user preference when PREFS_INITIAL_VALUES and PREF_CHANGED is invoked
+    this.discoveryStreamEnabled = ds && ds.value && JSON.parse(ds.value).enabled;
+    if (!this.discoveryStreamEnabled) {
+      this.initializeProperties();
+    }
+  }
+
+  initializeProperties() {
+    this.contentUpdateQueue = [];
     this.spocCampaignMap = new Map();
-    this.contentUpdateQueue = [];
     this.cache = new PersistentCache(SECTION_ID, true);
     this._prefs = new Prefs();
+    this.propertiesInitialized = true;
   }
 
   async onInit() {
     SectionsManager.enableSection(SECTION_ID);
+    if (this.discoveryStreamEnabled) {
+      return;
+    }
+
     try {
       const {options} = SectionsManager.sections.get(SECTION_ID);
       const apiKey = this.getApiKeyFromPref(options.api_key_pref);
       this.stories_endpoint = this.produceFinalEndpointUrl(options.stories_endpoint, apiKey);
       this.topics_endpoint = this.produceFinalEndpointUrl(options.topics_endpoint, apiKey);
       this.read_more_endpoint = options.read_more_endpoint;
       this.stories_referrer = options.stories_referrer;
       this.personalized = options.personalized;
@@ -94,17 +110,22 @@ this.TopStoriesFeed = class TopStoriesFe
   async clearCache() {
     await this.cache.set("stories", {});
     await this.cache.set("topics", {});
     await this.cache.set("spocs", {});
   }
 
   uninit() {
     this.storiesLoaded = false;
-    Services.obs.removeObserver(this, "idle-daily");
+    try {
+      Services.obs.removeObserver(this, "idle-daily");
+    } catch (e) {
+      // Attempt to remove unassociated observer which is possible when discovery stream
+      // is enabled and user never used activity stream experience
+    }
     SectionsManager.disableSection(SECTION_ID);
   }
 
   getPocketState(target) {
     const action = {type: at.POCKET_LOGGED_IN, data: pktApi.isUserLoggedIn()};
     this.store.dispatch(ac.OnlyToOneContent(action, target));
   }
 
@@ -638,20 +659,62 @@ this.TopStoriesFeed = class TopStoriesFe
           model_keys: data.model_keys,
         };
       }
       return true;
     }
     return false;
   }
 
-  async onAction(action) {
+  lazyLoadTopStories(dsPref) {
+    try {
+      this.discoveryStreamEnabled = JSON.parse(dsPref).enabled;
+    } catch (e) {
+      // Load activity stream top stories if fail to determine discovery stream state
+      this.discoveryStreamEnabled = false;
+    }
+
+    // Return without invoking initialization if top stories are loaded
+    if (this.storiesLoaded) {
+      return;
+    }
+
+    if (!this.discoveryStreamEnabled && !this.propertiesInitialized) {
+      this.initializeProperties();
+    }
+    this.init();
+  }
+
+  handleDisabled(action) {
     switch (action.type) {
-      case at.INIT:
-        this.init();
+      case at.PREFS_INITIAL_VALUES:
+        this.lazyLoadTopStories(action.data[DISCOVERY_STREAM_PREF]);
+        break;
+      case at.PREF_CHANGED:
+        if (action.data.name === DISCOVERY_STREAM_PREF) {
+          this.lazyLoadTopStories(action.data.value);
+        }
+        break;
+      case at.UNINIT:
+        this.uninit();
+        break;
+    }
+  }
+
+  async onAction(action) {
+    if (this.discoveryStreamEnabled) {
+      this.handleDisabled(action);
+      return;
+    }
+    switch (action.type) {
+      // Check for pref initial values to lazy load activity stream top stories
+      // Here we are not using usual INIT and relying on PREFS_INITIAL_VALUES
+      // to check discoverystream pref and load activity stream top stories only if needed.
+      case at.PREFS_INITIAL_VALUES:
+        this.lazyLoadTopStories(action.data[DISCOVERY_STREAM_PREF]);
         break;
       case at.SYSTEM_TICK:
         let stories;
         let topics;
         if (Date.now() - this.storiesLastUpdated >= STORIES_UPDATE_TIME) {
           stories = await this.fetchStories();
         }
         if (Date.now() - this.topicsLastUpdated >= TOPICS_UPDATE_TIME) {
@@ -705,16 +768,19 @@ this.TopStoriesFeed = class TopStoriesFe
                 .map(t => t.id);
               this.recordTopRecImpressions(topRecs);
             }
           }
         }
         break;
       }
       case at.PREF_CHANGED:
+        if (action.data.name === DISCOVERY_STREAM_PREF) {
+          this.lazyLoadTopStories(action.data.value);
+        }
         // Check if spocs was disabled. Remove them if they were.
         if (action.data.name === "showSponsored" && !action.data.value) {
           await this.removeSpocs();
         }
         if (action.data.name === "pocketCta") {
           this.dispatchPocketCta(action.data.value, true);
         }
         if (action.data.name === OPTIONS_PREF) {
--- a/browser/components/newtab/locales-src/de/strings.properties
+++ b/browser/components/newtab/locales-src/de/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext
 # 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
@@ -185,31 +186,31 @@ section_menu_action_add_topsite=Wichtige Seite hinzufügen
 section_menu_action_add_search_engine=Suchmaschine hinzufügen
 section_menu_action_move_up=Nach oben schieben
 section_menu_action_move_down=Nach unten schieben
 section_menu_action_privacy_notice=Datenschutzhinweis
 
 # LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the
 # firstrun of the browser, they give an introduction to Firefox and Sync.
 firstrun_title=Firefox für unterwegs
-firstrun_content=Nehmen Sie Ihre Lesezeichen, Chronik, Passwörter und andere Einstellungen auf allen Geräten mit.
-firstrun_learn_more_link=Jetzt mehr über Firefox-Konten erfahren
+firstrun_content=Nimm Lesezeichen, Chronik, Passwörter und andere Einstellungen mit auf alle deine Geräten.
+firstrun_learn_more_link=Weitere Infos zum Firefox-Konto
 
 # LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):
 # firstrun_form_sub_header is a continuation of firstrun_form_header, they are one sentence.
 # firstrun_form_header is displayed more boldly as the call to action.
 firstrun_form_header=E-Mail-Adresse eingeben
-firstrun_form_sub_header=um sich bei Firefox Sync anzumelden.
+firstrun_form_sub_header=um dich bei Firefox Sync anzumelden
 
 firstrun_email_input_placeholder=E-Mail
 firstrun_invalid_input=Gültige E-Mail-Adresse erforderlich
 
 # LOCALIZATION NOTE (firstrun_extra_legal_links): {terms} is equal to firstrun_terms_of_service, and
 # {privacy} is equal to firstrun_privacy_notice. {terms} and {privacy} are clickable links.
-firstrun_extra_legal_links=Indem Sie fortfahren, stimmen Sie den {terms} und dem {privacy} zu.
+firstrun_extra_legal_links=Indem du fortfährst, stimmst du unseren {terms} und dem {privacy} zu.
 firstrun_terms_of_service=Nutzungsbedingungen
 firstrun_privacy_notice=Datenschutzhinweis
 
 firstrun_continue_to_login=Weiter
 firstrun_skip_login=Diesen Schritt überspringen
 
 # LOCALIZATION NOTE (context_menu_title): Action tooltip to open a context menu
 context_menu_title=Menü öffnen
--- a/browser/components/newtab/locales-src/it/strings.properties
+++ b/browser/components/newtab/locales-src/it/strings.properties
@@ -102,14 +102,14 @@ section_menu_action_move_down=Sposta giù
 section_menu_action_privacy_notice=Informativa sulla privacy
 firstrun_title=Porta Firefox con te
 firstrun_content=Ritrova segnalibri, cronologia, password e altre impostazioni su tutti i tuoi dispositivi.
 firstrun_learn_more_link=Ulteriori informazioni sull’account Firefox
 firstrun_form_header=Inserisci la tua email
 firstrun_form_sub_header=per utilizzare Firefox Sync.
 firstrun_email_input_placeholder=Email
 firstrun_invalid_input=Inserire un indirizzo email valido
-firstrun_extra_legal_links=Proseguendo, accetto le {terms} e l’{privacy}.
+firstrun_extra_legal_links=Proseguendo, accetti le {terms} e l’{privacy}.
 firstrun_terms_of_service=condizioni di utilizzo del servizio
 firstrun_privacy_notice=informativa sulla privacy
 firstrun_continue_to_login=Continua
 firstrun_skip_login=Ignora questo passaggio
 context_menu_title=Apri menu
--- a/browser/components/newtab/locales-src/ka/strings.properties
+++ b/browser/components/newtab/locales-src/ka/strings.properties
@@ -1,15 +1,15 @@
 newtab_page_title=ახალი ჩანართი
 
 header_top_sites=რჩეული საიტები
 header_highlights=მნიშვნელოვანი
 # LOCALIZATION NOTE(header_recommended_by): This is followed by the name
 # of the corresponding content provider.
-header_recommended_by={provider}-ის შემოთავაზებული
+header_recommended_by={provider} გირჩევთ
 
 # LOCALIZATION NOTE(context_menu_button_sr): This is for screen readers when
 # the context menu button is focused/active. Title is the label or hostname of
 # the site.
 context_menu_button_sr=კონტექსტური მენიუს გახსნა {title}
 
 # LOCALIZATION NOTE(section_context_menu_button_sr): This is for screen readers when
 # the section edit context menu button is focused/active.
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext=კარგი, გასაგებია
 # 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=საძიებო ველი
@@ -152,17 +153,17 @@ pocket_more_reccommendations=მეტი შემოთავაზებები
 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}-იდან ახალი რჩეული სტატიების მისაღებად, მოგვიანებით შემოიარეთ. თუ ვერ ითმენთ, აირჩიეთ რომელიმე მოთხოვნადი თემა, ახალი საინტერესო სტატიების მოსაძიებლად.
+topstories_empty_state=უკვე ყველაფერს გაეცანით. მოგვიანებით შემოიარეთ მეტი რჩეული სტატიის სანახავად, რომელსაც {provider} მოგაწვდით. ვერ ითმენთ? აირჩიეთ რომელიმე ფართოდ გავრცელებული საკითხი, ახალი საინტერესო სტატიების მოსაძიებლად.
 
 # LOCALIZATION NOTE (manual_migration_explanation2): This message is shown to encourage users to
 # import their browser profile from another browser they might be using.
 manual_migration_explanation2=გადმოიტანეთ სხვა ბრაუზერებიდან თქვენი სანიშნები, ისტორია და პაროლები Firefox-ში.
 # LOCALIZATION NOTE (manual_migration_cancel_button): This message is shown on a button that cancels the
 # process of importing another browser’s profile into Firefox.
 manual_migration_cancel_button=არა, გმადლობთ
 # LOCALIZATION NOTE (manual_migration_import_button): This message is shown on a button that starts the process
--- a/browser/components/newtab/locales-src/lt/strings.properties
+++ b/browser/components/newtab/locales-src/lt/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext
 # 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“ pradžios turinys
 prefs_home_description=Pasirinkite, kokį turinį norite matyti „Firefox“ pradžios ekrane
 
 prefs_content_discovery_header=„Firefox“ pradžios tinklalapis
+
 prefs_content_discovery_description=„Firefox“ turinio atradimas pradžios tinklalapyje leidžia atrasti aukštos kokybės ir jums galimai įdomius straipsnius iš interneto.
 prefs_content_discovery_button=Išjungti turinio atradimą
 
 # 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} eilutė;{num} eilutės;{num} eilučių
 prefs_search_header=Paieška internete
@@ -186,17 +187,17 @@ section_menu_action_add_search_engine=Pridėti ieškyklę
 section_menu_action_move_up=Pakelti
 section_menu_action_move_down=Nuleisti
 section_menu_action_privacy_notice=Privatumo pranešimas
 
 # LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the
 # firstrun of the browser, they give an introduction to Firefox and Sync.
 firstrun_title=Pasiimkite „Firefox“ su savimi
 firstrun_content=Turėkite savo adresyną, žurnalą, slaptažodžius ir kitas nuostatas visuose savo įrenginiuose.
-firstrun_learn_more_link=Sužinokite daugiau apie „Firefox“ paskyras
+firstrun_learn_more_link=Sužinokite apie „Firefox“ paskyras daugiau
 
 # LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):
 # firstrun_form_sub_header is a continuation of firstrun_form_header, they are one sentence.
 # firstrun_form_header is displayed more boldly as the call to action.
 firstrun_form_header=Įveskite savo el. paštą
 firstrun_form_sub_header=norėdami tęsti su „Firefox Sync“.
 
 firstrun_email_input_placeholder=El. paštas
--- a/browser/components/newtab/locales-src/sk/strings.properties
+++ b/browser/components/newtab/locales-src/sk/strings.properties
@@ -88,16 +88,17 @@ section_disclaimer_topstories_buttontext
 # 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
@@ -156,17 +157,17 @@ 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.
 topstories_empty_state=Už ste prečítali všetko. Ďalšie príbehy zo služby {provider} tu nájdete opäť neskôr. Nemôžete sa dočkať? Vyberte si populárnu tému a pozrite sa na ďalšie skvelé príbehy z celého webu.
 
 # LOCALIZATION NOTE (manual_migration_explanation2): This message is shown to encourage users to
 # import their browser profile from another browser they might be using.
-manual_migration_explanation2=Vyskúšajte Firefox so záložkami, históriou prehliadania a heslami s iných prehliadačov.
+manual_migration_explanation2=Vyskúšajte Firefox so záložkami, históriou prehliadania a heslami z iných prehliadačov.
 # LOCALIZATION NOTE (manual_migration_cancel_button): This message is shown on a button that cancels the
 # process of importing another browser’s profile into Firefox.
 manual_migration_cancel_button=Nie, ďakujem
 # LOCALIZATION NOTE (manual_migration_import_button): This message is shown on a button that starts the process
 # of importing another browser’s profile profile into Firefox.
 manual_migration_import_button=Importovať teraz
 
 # LOCALIZATION NOTE (error_fallback_default_*): This message and suggested
--- a/browser/components/newtab/locales-src/vi/strings.properties
+++ b/browser/components/newtab/locales-src/vi/strings.properties
@@ -35,17 +35,17 @@ menu_action_open_private_window=Mở trong cửa sổ riêng tư mới
 menu_action_dismiss=Bỏ qua
 menu_action_delete=Xóa khỏi lịch sử
 menu_action_pin=Ghim
 menu_action_unpin=Bỏ ghim
 confirm_history_delete_p1=Bạn có chắc bạn muốn xóa bỏ mọi thứ của trang này từ lịch sử?
 # LOCALIZATION NOTE (confirm_history_delete_notice_p2): this string is displayed in
 # the same dialog as confirm_history_delete_p1. "This action" refers to deleting a
 # page from history.
-confirm_history_delete_notice_p2=Hành động này không thể hoàn tác.
+confirm_history_delete_notice_p2=Thao tác này không thể hoàn tác được.
 menu_action_save_to_pocket=Lưu vào Pocket
 menu_action_delete_pocket=Xóa khỏi Pocket
 menu_action_archive_pocket=Lưu trữ trong Pocket
 
 # LOCALIZATION NOTE (menu_action_show_file_*): These are platform specific strings
 # found in the context menu of an item that has been downloaded. The intention behind
 # "this action" is that it will show where the downloaded file exists on the file system
 # for each operating system.
--- a/browser/components/newtab/prerendered/locales/de/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/de/activity-stream-strings.js
@@ -89,21 +89,21 @@ window.gActivityStreamStrings = {
   "section_menu_action_manage_section": "Abschnitt verwalten",
   "section_menu_action_manage_webext": "Erweiterung verwalten",
   "section_menu_action_add_topsite": "Wichtige Seite hinzufügen",
   "section_menu_action_add_search_engine": "Suchmaschine hinzufügen",
   "section_menu_action_move_up": "Nach oben schieben",
   "section_menu_action_move_down": "Nach unten schieben",
   "section_menu_action_privacy_notice": "Datenschutzhinweis",
   "firstrun_title": "Firefox für unterwegs",
-  "firstrun_content": "Nehmen Sie Ihre Lesezeichen, Chronik, Passwörter und andere Einstellungen auf allen Geräten mit.",
-  "firstrun_learn_more_link": "Jetzt mehr über Firefox-Konten erfahren",
+  "firstrun_content": "Nimm Lesezeichen, Chronik, Passwörter und andere Einstellungen mit auf alle deine Geräten.",
+  "firstrun_learn_more_link": "Weitere Infos zum Firefox-Konto",
   "firstrun_form_header": "E-Mail-Adresse eingeben",
-  "firstrun_form_sub_header": "um sich bei Firefox Sync anzumelden.",
+  "firstrun_form_sub_header": "um dich bei Firefox Sync anzumelden",
   "firstrun_email_input_placeholder": "E-Mail",
   "firstrun_invalid_input": "Gültige E-Mail-Adresse erforderlich",
-  "firstrun_extra_legal_links": "Indem Sie fortfahren, stimmen Sie den {terms} und dem {privacy} zu.",
+  "firstrun_extra_legal_links": "Indem du fortfährst, stimmst du unseren {terms} und dem {privacy} zu.",
   "firstrun_terms_of_service": "Nutzungsbedingungen",
   "firstrun_privacy_notice": "Datenschutzhinweis",
   "firstrun_continue_to_login": "Weiter",
   "firstrun_skip_login": "Diesen Schritt überspringen",
   "context_menu_title": "Menü öffnen"
 };
--- a/browser/components/newtab/prerendered/locales/it/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/it/activity-stream-strings.js
@@ -95,15 +95,15 @@ window.gActivityStreamStrings = {
   "section_menu_action_privacy_notice": "Informativa sulla privacy",
   "firstrun_title": "Porta Firefox con te",
   "firstrun_content": "Ritrova segnalibri, cronologia, password e altre impostazioni su tutti i tuoi dispositivi.",
   "firstrun_learn_more_link": "Ulteriori informazioni sull’account Firefox",
   "firstrun_form_header": "Inserisci la tua email",
   "firstrun_form_sub_header": "per utilizzare Firefox Sync.",
   "firstrun_email_input_placeholder": "Email",
   "firstrun_invalid_input": "Inserire un indirizzo email valido",
-  "firstrun_extra_legal_links": "Proseguendo, accetto le {terms} e l’{privacy}.",
+  "firstrun_extra_legal_links": "Proseguendo, accetti le {terms} e l’{privacy}.",
   "firstrun_terms_of_service": "condizioni di utilizzo del servizio",
   "firstrun_privacy_notice": "informativa sulla privacy",
   "firstrun_continue_to_login": "Continua",
   "firstrun_skip_login": "Ignora questo passaggio",
   "context_menu_title": "Apri menu"
 };
--- a/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered-noscripts.html
+++ b/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered-noscripts.html
@@ -5,12 +5,12 @@
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
     <title>ახალი ჩანართი</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="header-asrouter-container" role="presentation"></div>
-    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket-ის შემოთავაზებული</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
+    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket გირჩევთ</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
     <div id="footer-asrouter-container" role="presentation"></div>
   </body>
 </html>
--- a/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered.html
+++ b/browser/components/newtab/prerendered/locales/ka/activity-stream-prerendered.html
@@ -5,17 +5,17 @@
     <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';">
     <title>ახალი ჩანართი</title>
     <link rel="icon" type="image/png" href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css" />
     <link rel="stylesheet" href="resource://activity-stream/css/activity-stream.css" />
   </head>
   <body class="activity-stream">
     <div id="header-asrouter-container" role="presentation"></div>
-    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket-ის შემოთავაზებული</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
+    <div id="root"><div><div class="outer-wrapper fixed-to-top"><main><div class="non-collapsible-section"><div class="search-wrapper"><div class="search-inner-wrapper"><label for="newtab-search-text" class="search-label"><span class="sr-only"><span>ინტერნეტში ძიება</span></span></label><input type="search" id="newtab-search-text" maxLength="256" placeholder="ინტერნეტში ძიება" title="ინტერნეტში ძიება"/><button id="searchSubmit" class="search-button" title="ძიება"><span class="sr-only"><span>ძიება</span></span></button></div></div></div><div class="body-wrapper"><div class="sections-list"><section class="collapsible-section top-sites animation-enabled" data-section-id="topsites"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-topsites"></span><span>რჩეული საიტები</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="top-sites-list"><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder "><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li><li class="top-site-outer placeholder hide-for-narrow"><div class="top-site-inner"><a tabindex="0" draggable="true"><div class="tile" aria-hidden="true"><div class="screenshot" style="background-image:none"></div></div><div class="title "><span dir="auto"></span></div></a><button class="context-menu-button edit-button icon" title="საიტის ჩასწორება"></button></div></li></ul><div class="edit-topsites-wrapper"></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="topstories"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-pocket"></span><span>Pocket გირჩევთ</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul><div class="top-stories-bottom-container"><div class="wrapper-more-recommendations"></div></div></div></section><section class="collapsible-section section normal-cards animation-enabled" data-section-id="highlights"><div class="section-top-bar"><h3 class="section-title"><span class="click-target-container"><span class="click-target"><span class="icon icon-small-spacer icon-highlights"></span><span>მნიშვნელოვანი</span></span><span class="click-target"></span><span class="learn-more-link-wrapper"></span></span></h3><div><button class="context-menu-button icon" title="მენიუს გახსნა"><span class="sr-only"><span>გვერდის ნაწილების პარამეტრები</span></span></button></div></div><div class="section-body"><ul class="section-list" style="padding:0"></ul></div></section></div><div class="prefs-button"><button class="icon icon-settings" title="მოირგეთ ახალი ჩანართის გვერდი"></button></div></div></main></div></div></div>
     <div id="footer-asrouter-container" role="presentation"></div>
     <script src="resource://activity-stream/prerendered/static/activity-stream-initial-state.js"></script>
     <script src="chrome://browser/content/contentSearchUI.js"></script>
     <script src="chrome://browser/content/contentTheme.js"></script>
     <script src="resource://activity-stream/vendor/react.js"></script>
     <script src="resource://activity-stream/vendor/react-dom.js"></script>
     <script src="resource://activity-stream/vendor/prop-types.js"></script>
     <script src="resource://activity-stream/vendor/react-intl.js"></script>
--- a/browser/components/newtab/prerendered/locales/ka/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/ka/activity-stream-strings.js
@@ -1,14 +1,14 @@
 // Note - this is a generated ka file.
 window.gActivityStreamStrings = {
   "newtab_page_title": "ახალი ჩანართი",
   "header_top_sites": "რჩეული საიტები",
   "header_highlights": "მნიშვნელოვანი",
-  "header_recommended_by": "{provider}-ის შემოთავაზებული",
+  "header_recommended_by": "{provider} გირჩევთ",
   "context_menu_button_sr": "კონტექსტური მენიუს გახსნა {title}",
   "section_context_menu_button_sr": "გვერდის ნაწილების პარამეტრები",
   "type_label_visited": "მონახულებული",
   "type_label_bookmarked": "ჩანიშნული",
   "type_label_recommended": "პოპულარული",
   "type_label_pocket": "შენახულია Pocket-ში",
   "type_label_downloaded": "ჩამოტვირთული",
   "menu_action_bookmark": "ჩანიშვნა",
@@ -75,17 +75,17 @@ window.gActivityStreamStrings = {
   "topsites_form_image_validation": "სურათი ვერ ჩაიტვირთა. სცადეთ სხვა URL-ბმული.",
   "pocket_read_more": "პოპულარული თემები:",
   "pocket_read_even_more": "მეტი სიახლის ნახვა",
   "pocket_more_reccommendations": "მეტი შემოთავაზებები",
   "pocket_how_it_works": "როგორ მუშაობს",
   "pocket_cta_button": "გამოიყენეთ Pocket",
   "pocket_cta_text": "გადაინახეთ სასურველი შიგთავსი Pocket-ში და მიეცით გონებას საკვები, შთამბეჭდავი საკითხავი მასალის სახით.",
   "highlights_empty_state": "დაიწყეთ გვერდების დათვალიერება და აქ გამოჩნდება თქვენთვის სასურველი სტატიები, ვიდეოები და ბოლოს მონახულებული ან ჩანიშნული საიტები.",
-  "topstories_empty_state": "უკვე ყველაფერი წაკითხული გაქვთ. {provider}-იდან ახალი რჩეული სტატიების მისაღებად, მოგვიანებით შემოიარეთ. თუ ვერ ითმენთ, აირჩიეთ რომელიმე მოთხოვნადი თემა, ახალი საინტერესო სტატიების მოსაძიებლად.",
+  "topstories_empty_state": "უკვე ყველაფერს გაეცანით. მოგვიანებით შემოიარეთ მეტი რჩეული სტატიის სანახავად, რომელსაც {provider} მოგაწვდით. ვერ ითმენთ? აირჩიეთ რომელიმე ფართოდ გავრცელებული საკითხი, ახალი საინტერესო სტატიების მოსაძიებლად.",
   "error_fallback_default_info": "სამწუხაროდ, შიგთავსის ჩატვირთვისას რაღაც ხარვეზი წარმოიქმნა.",
   "error_fallback_default_refresh_suggestion": "განაახლეთ გვერდი და სცადეთ ხელახლა.",
   "section_menu_action_remove_section": "ამ ნაწილის მოცილება",
   "section_menu_action_collapse_section": "ამ ნაწილის აკეცვა",
   "section_menu_action_expand_section": "ამ ნაწილის გაშლა",
   "section_menu_action_manage_section": "გვერდის ნაწილების მართვა",
   "section_menu_action_manage_webext": "გაფართოების მართვა",
   "section_menu_action_add_topsite": "რჩეული საიტის დამატება",
--- a/browser/components/newtab/prerendered/locales/lt/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/lt/activity-stream-strings.js
@@ -90,17 +90,17 @@ window.gActivityStreamStrings = {
   "section_menu_action_manage_webext": "Tvarkyti priedą",
   "section_menu_action_add_topsite": "Pridėti lankomą svetainę",
   "section_menu_action_add_search_engine": "Pridėti ieškyklę",
   "section_menu_action_move_up": "Pakelti",
   "section_menu_action_move_down": "Nuleisti",
   "section_menu_action_privacy_notice": "Privatumo pranešimas",
   "firstrun_title": "Pasiimkite „Firefox“ su savimi",
   "firstrun_content": "Turėkite savo adresyną, žurnalą, slaptažodžius ir kitas nuostatas visuose savo įrenginiuose.",
-  "firstrun_learn_more_link": "Sužinokite daugiau apie „Firefox“ paskyras",
+  "firstrun_learn_more_link": "Sužinokite apie „Firefox“ paskyras daugiau",
   "firstrun_form_header": "Įveskite savo el. paštą",
   "firstrun_form_sub_header": "norėdami tęsti su „Firefox Sync“.",
   "firstrun_email_input_placeholder": "El. paštas",
   "firstrun_invalid_input": "Reikalingas galiojantis el. pašto adresas",
   "firstrun_extra_legal_links": "Tęsdami sutinkate su {terms} ir {privacy}.",
   "firstrun_terms_of_service": "paslaugos teikimo nuostatais",
   "firstrun_privacy_notice": "Privatumo pranešimu",
   "firstrun_continue_to_login": "Tęsti",
--- a/browser/components/newtab/prerendered/locales/vi/activity-stream-strings.js
+++ b/browser/components/newtab/prerendered/locales/vi/activity-stream-strings.js
@@ -15,17 +15,17 @@ window.gActivityStreamStrings = {
   "menu_action_remove_bookmark": "Xóa đánh dấu",
   "menu_action_open_new_window": "Mở trong cửa sổ mới",
   "menu_action_open_private_window": "Mở trong cửa sổ riêng tư mới",
   "menu_action_dismiss": "Bỏ qua",
   "menu_action_delete": "Xóa khỏi lịch sử",
   "menu_action_pin": "Ghim",
   "menu_action_unpin": "Bỏ ghim",
   "confirm_history_delete_p1": "Bạn có chắc bạn muốn xóa bỏ mọi thứ của trang này từ lịch sử?",
-  "confirm_history_delete_notice_p2": "Hành động này không thể hoàn tác.",
+  "confirm_history_delete_notice_p2": "Thao tác này không thể hoàn tác được.",
   "menu_action_save_to_pocket": "Lưu vào Pocket",
   "menu_action_delete_pocket": "Xóa khỏi Pocket",
   "menu_action_archive_pocket": "Lưu trữ trong Pocket",
   "menu_action_show_file_mac_os": "Hiển thị trong Finder",
   "menu_action_show_file_windows": "Mở thư mục chứa",
   "menu_action_show_file_linux": "Mở thư mục chứa",
   "menu_action_show_file_default": "Hiện tập tin",
   "menu_action_open_file": "Mở tập tin",
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -230,16 +230,30 @@ describe("ASRouter", () => {
       assert.calledOnce(ASRouterPreferences.init);
       assert.calledWith(ASRouterPreferences.addListener, Router.onPrefChange);
     });
     it("should call ASRouterPreferences.uninit and remove the listener on uninit", () => {
       Router.uninit();
       assert.calledOnce(ASRouterPreferences.uninit);
       assert.calledWith(ASRouterPreferences.removeListener, Router.onPrefChange);
     });
+    it("should send a AS_ROUTER_TARGETING_UPDATE message", async () => {
+      const messageTargeted = {id: "1", campaign: "foocampaign", targeting: "true"};
+      const messageNotTargeted = {id: "2", campaign: "foocampaign"};
+      await Router.setState({messages: [messageTargeted, messageNotTargeted]});
+      sandbox.stub(ASRouterTargeting, "isMatch").resolves(false);
+
+      await Router.onPrefChange("services.sync.username");
+
+      assert.calledOnce(channel.sendAsyncMessage);
+      const [, {type, data}] = channel.sendAsyncMessage.firstCall.args;
+      assert.equal(type, "AS_ROUTER_TARGETING_UPDATE");
+      assert.equal(data[0], messageTargeted.id);
+      assert.lengthOf(data, 1);
+    });
     it("should call loadMessagesFromAllProviders on pref change", () => {
       sandbox.spy(Router, "loadMessagesFromAllProviders");
 
       ASRouterPreferences.observe(null, null, MESSAGE_PROVIDER_PREF_NAME);
 
       assert.calledOnce(Router.loadMessagesFromAllProviders);
     });
     it("should update the list of providers on pref change", () => {
--- a/browser/components/newtab/test/unit/asrouter/ASRouterPreferences.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouterPreferences.test.js
@@ -8,18 +8,19 @@ const CFR_USER_PREF_ADDONS = "browser.ne
 const CFR_USER_PREF_FEATURES = "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features";
 
 /** NUMBER_OF_PREFS_TO_OBSERVE includes:
  *  1. asrouter.providers. pref branch
  *  2. asrouter.devtoolsEnabled
  *  3. browser.newtabpage.activity-stream.feeds.snippets (user preference - snippets)
  *  4. browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons (user preference - cfr)
  *  4. browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features (user preference - cfr)
+ *  5. services.sync.username
  */
-const NUMBER_OF_PREFS_TO_OBSERVE = 5;
+const NUMBER_OF_PREFS_TO_OBSERVE = 6;
 
 describe("ASRouterPreferences", () => {
   let ASRouterPreferences;
   let sandbox;
   let addObserverStub;
   let stringPrefStub;
   let boolPrefStub;
 
--- a/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/asrouter-content.test.jsx
@@ -299,10 +299,23 @@ describe("ASRouterUISurface", () => {
       assert.calledOnce(ASRouterUtils.sendTelemetry);
       const [payload] = ASRouterUtils.sendTelemetry.firstCall.args;
 
       assert.propertyVal(payload, "message_id", FAKE_MESSAGE.id);
       assert.propertyVal(payload, "event", "IMPRESSION");
       assert.propertyVal(payload, "action", `${FAKE_MESSAGE.provider}_user_event`);
       assert.propertyVal(payload, "source", "NEWTAB_FOOTER_BAR");
     });
+
+    it("should call .sendTelemetry with the right message data when a bundle is dismissed", () => {
+      wrapper.instance().dismissBundle([{id: 1}, {id: 2}, {id: 3}])();
+
+      assert.calledOnce(ASRouterUtils.sendTelemetry);
+      assert.calledWith(ASRouterUtils.sendTelemetry, {
+        action: "onboarding_user_event",
+        event: "DISMISS",
+        id: "onboarding-cards",
+        message_id: "1,2,3",
+        source: "onboarding-cards",
+      });
+    });
   });
 });
--- a/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
@@ -31,16 +31,19 @@ describe("<Trailhead>", () => {
     dispatch = sandbox.stub();
     onAction = sandbox.stub();
     sandbox.stub(global, "fetch")
       .resolves({ok: true, status: 200, json: () => Promise.resolve({flowId: 123, flowBeginTime: 456})});
 
     dummyNode = document.createElement("body");
     sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
     const fakeDocument = {
+      get activeElement() {
+        return dummyNode;
+      },
       get body() {
         return dummyNode;
       },
       getElementById() {
         return dummyNode;
       },
     };
 
@@ -63,19 +66,30 @@ describe("<Trailhead>", () => {
     assert.calledWith(dispatch, ac.UserEvent({event: at.SKIPPED_SIGNIN, value: {has_flow_params: false}}));
   });
 
   it("should NOT emit UserEvent SKIPPED_SIGNIN when closeModal is triggered by visibilitychange event", () => {
     wrapper.instance().closeModal({type: "visibilitychange"});
     assert.notCalled(dispatch);
   });
 
-  it("should emit UserEvent SUBMIT_EMAIL when you submit the form", () => {
+  it("should prevent submissions with no email", () => {
+    const form = wrapper.find("form");
+    const preventDefault = sandbox.stub();
+
+    form.simulate("submit", {preventDefault});
+
+    assert.calledOnce(preventDefault);
+    assert.notCalled(dispatch);
+  });
+  it("should emit UserEvent SUBMIT_EMAIL when you submit a valid email", () => {
     let form = wrapper.find("form");
     assert.ok(form.exists());
+    form.getDOMNode().elements.email.value = "a@b.c";
+
     form.simulate("submit");
 
     assert.calledOnce(dispatch);
     assert.isUserEventAction(dispatch.firstCall.args[0]);
     assert.calledWith(dispatch, ac.UserEvent({event: at.SUBMIT_EMAIL, value: {has_flow_params: false}}));
   });
 
   it("should add utm_* query params to card actions", () => {
--- a/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/ReturnToAMO.test.jsx
@@ -1,22 +1,84 @@
 import {mountWithIntl} from "test/unit/utils";
 import React from "react";
 import {ReturnToAMO} from "content-src/asrouter/templates/ReturnToAMO/ReturnToAMO";
 
 describe("<ReturnToAMO>", () => {
   let dispatch;
   let onReady;
+  let sandbox;
+  let wrapper;
+  let sendUserActionTelemetryStub;
+  let content;
   beforeEach(() => {
-    dispatch = sinon.stub();
-    onReady = sinon.stub();
-    const content = {
+    sandbox = sinon.createSandbox();
+    dispatch = sandbox.stub();
+    onReady = sandbox.stub();
+    sendUserActionTelemetryStub = sandbox.stub();
+    content = {
       primary_button: {},
       secondary_button: {},
     };
+  });
 
-    mountWithIntl(<ReturnToAMO onReady={onReady} dispatch={dispatch} content={content} />);
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  describe("not mounted", () => {
+    it("should send an IMPRESSION on mount", () => {
+      assert.notCalled(sendUserActionTelemetryStub);
+
+      wrapper = mountWithIntl(<ReturnToAMO onReady={onReady}
+        dispatch={dispatch}
+        content={content}
+        onBlock={sandbox.stub()}
+        onAction={sandbox.stub()}
+        UISurface="NEWTAB_OVERLAY"
+        sendUserActionTelemetry={sendUserActionTelemetryStub} />);
+
+      assert.calledOnce(sendUserActionTelemetryStub);
+      assert.calledWithExactly(sendUserActionTelemetryStub, {
+        event: "IMPRESSION",
+        id: wrapper.instance().props.UISurface,
+      });
+    });
   });
 
-  it("should call onReady on componentDidMount", () => {
-    assert.calledOnce(onReady);
+  describe("mounted", () => {
+    beforeEach(() => {
+      wrapper = mountWithIntl(<ReturnToAMO onReady={onReady}
+        dispatch={dispatch}
+        content={content}
+        onBlock={sandbox.stub()}
+        onAction={sandbox.stub()}
+        UISurface="NEWTAB_OVERLAY"
+        sendUserActionTelemetry={sendUserActionTelemetryStub} />);
+
+      // Clear the IMPRESSION ping
+      sendUserActionTelemetryStub.reset();
+    });
+
+    it("should call onReady on componentDidMount", () => {
+      assert.calledOnce(onReady);
+    });
+
+    it("should send telemetry on block", () => {
+      wrapper.instance().onBlockButton();
+
+      assert.calledOnce(sendUserActionTelemetryStub);
+      assert.calledWithExactly(sendUserActionTelemetryStub, {
+        event: "BLOCK",
+        id: wrapper.instance().props.UISurface,
+      });
+    });
+
+    it("should send telemetry on install", () => {
+      wrapper.instance().onClickAddExtension();
+
+      assert.calledWithExactly(sendUserActionTelemetryStub, {
+        event: "INSTALL",
+        id: wrapper.instance().props.UISurface,
+      });
+    });
   });
 });
--- a/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
+++ b/browser/components/newtab/test/unit/lib/AboutPreferences.test.js
@@ -108,17 +108,16 @@ describe("AboutPreferences Feed", () => 
 
       await instance.observe(window, PREFERENCES_LOADED_EVENT);
 
       assert.calledOnce(stub);
       assert.equal(stub.firstCall.args[2][0].id, "search");
       assert.equal(stub.firstCall.args[2][1].id, "topsites");
       assert.equal(stub.firstCall.args[2][2].id, "topstories");
       assert.isEmpty(stub.firstCall.args[2][2].rowsPref);
-      assert.equal(stub.firstCall.args[2][2].pref.descString.id, "prefs_content_discovery_description");
     });
   });
   describe("#strings", () => {
     let activityStreamLocale;
     let fetchStub;
     let fetchText;
     beforeEach(() => {
       global.Cc["@mozilla.org/browser/aboutnewtab-service;1"] = {
--- a/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
@@ -1200,29 +1200,16 @@ describe("DiscoveryStreamFeed", () => {
       assert.calledWithMatch(feed.store.dispatch, {
         data: {
           name: CONFIG_PREF_NAME,
           value: JSON.stringify({enabled: true, other: "value", layout_endpoint: "foo.com"}),
         },
         type: at.SET_PREF,
       });
     });
-    it("should disable opt-out when setting config enabled", () => {
-      sandbox.spy(feed.store, "dispatch");
-
-      feed.onAction({type: at.DISCOVERY_STREAM_CONFIG_SET_VALUE, data: {name: "enabled", value: true}});
-
-      assert.calledWithMatch(feed.store.dispatch, {
-        data: {
-          name: "discoverystream.optOut.0",
-          value: false,
-        },
-        type: at.SET_PREF,
-      });
-    });
   });
 
   describe("#onAction: DISCOVERY_STREAM_CONFIG_CHANGE", () => {
     it("should call this.loadLayout if config.enabled changes to true ", async () => {
       sandbox.stub(feed.cache, "set").returns(Promise.resolve());
       // First initialize
       await feed.onAction({type: at.INIT});
       assert.isFalse(feed.loaded);
@@ -1279,55 +1266,32 @@ describe("DiscoveryStreamFeed", () => {
       await feed.onAction({type: at.DISCOVERY_STREAM_CONFIG_CHANGE});
 
       assert.notCalled(feed.loadLayout);
       assert.calledOnce(feed.resetCache);
       assert.isFalse(feed.loaded);
     });
   });
 
-  describe("#onAction: DISCOVERY_STREAM_OPT_OUT", () => {
-    it("should update opt-out pref", async () => {
-      sandbox.spy(feed.store, "dispatch");
-
-      await feed.onAction({type: at.DISCOVERY_STREAM_OPT_OUT});
-
-      assert.calledWithMatch(feed.store.dispatch, {
-        data: {
-          name: "discoverystream.optOut.0",
-          value: true,
-        },
-        type: at.SET_PREF,
-      });
-    });
-  });
-
   describe("#onAction: UNINIT", () => {
     it("should reset pref cache", async () => {
       feed._prefCache = {cached: "value"};
 
       await feed.onAction({type: at.UNINIT});
 
       assert.deepEqual(feed._prefCache, {});
     });
   });
 
   describe("#onAction: PREF_CHANGED", () => {
     it("should update state.DiscoveryStream.config when the pref changes", async () => {
       setPref(CONFIG_PREF_NAME, {enabled: true, show_spocs: false, layout_endpoint: "foo"});
 
       assert.deepEqual(feed.store.getState().DiscoveryStream.config, {enabled: true, show_spocs: false, layout_endpoint: "foo"});
     });
-    it("should handle pref changes when opt out changes", async () => {
-      setPref(CONFIG_PREF_NAME, {enabled: true, show_spocs: false, layout_endpoint: "foo"});
-
-      setPref("discoverystream.optOut.0", true);
-
-      assert.deepEqual(feed.store.getState().DiscoveryStream.config, {enabled: false, show_spocs: false, layout_endpoint: "foo"});
-    });
     it("should fire loadSpocs is showSponsored pref changes", async () => {
       sandbox.stub(feed, "loadSpocs").returns(Promise.resolve());
 
       await feed.onAction({type: at.PREF_CHANGED, data: {name: "showSponsored"}});
 
       assert.calledOnce(feed.loadSpocs);
     });
   });
--- a/browser/components/newtab/test/unit/lib/TopStoriesFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/TopStoriesFeed.test.js
@@ -86,38 +86,135 @@ describe("Top Stories Feed", () => {
     instance.store = {getState() { return {Prefs: {values: {showSponsored: true}}}; }, dispatch: sinon.spy()};
     instance.storiesLastUpdated = 0;
     instance.topicsLastUpdated = 0;
   });
   afterEach(() => {
     globals.restore();
     clock.restore();
   });
+
+  describe("#lazyloading TopStories", () => {
+    beforeEach(() => {
+      instance.discoveryStreamEnabled = true;
+    });
+    it("should bind parseOptions to SectionsManager.onceInitialized when discovery stream is true", () => {
+      instance.discoveryStreamEnabled = false;
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: true})}});
+      assert.calledOnce(sectionsManagerStub.onceInitialized);
+    });
+    it("should bind parseOptions to SectionsManager.onceInitialized when discovery stream is false", () => {
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: false})}});
+      assert.calledOnce(sectionsManagerStub.onceInitialized);
+    });
+    it("Should initialize properties once while lazy loading if not initialized earlier", () => {
+      instance.discoveryStreamEnabled = false;
+      instance.propertiesInitialized = false;
+      sinon.stub(instance, "initializeProperties");
+      instance.lazyLoadTopStories();
+      assert.calledOnce(instance.initializeProperties);
+    });
+    it("should not re-initialize properties", () => {
+      // For discovery stream experience disabled TopStoriesFeed properties
+      // are initialized in constructor and should not be called again while lazy loading topstories
+      sinon.stub(instance, "initializeProperties");
+      instance.discoveryStreamEnabled = false;
+      instance.propertiesInitialized = true;
+      instance.lazyLoadTopStories();
+      assert.notCalled(instance.initializeProperties);
+    });
+    it("should have early exit onInit when discovery is true", async () => {
+      sinon.stub(instance, "doContentUpdate");
+      await instance.onInit();
+      assert.notCalled(instance.doContentUpdate);
+      assert.isUndefined(instance.storiesLoaded);
+    });
+    it("should complete onInit when discovery is false", async () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.stub(instance, "doContentUpdate");
+      await instance.onInit();
+      assert.calledOnce(instance.doContentUpdate);
+      assert.isTrue(instance.storiesLoaded);
+    });
+    it("should handle limited actions when discoverystream is enabled", async () => {
+      sinon.spy(instance, "handleDisabled");
+      sinon.stub(instance, "getPocketState");
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: true})}});
+      assert.calledOnce(instance.handleDisabled);
+
+      instance.onAction({type: at.NEW_TAB_REHYDRATED, meta: {fromTarget: {}}});
+      assert.notCalled(instance.getPocketState);
+    });
+    it("should handle NEW_TAB_REHYDRATED when discoverystream is disabled", async () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.spy(instance, "handleDisabled");
+      sinon.stub(instance, "getPocketState");
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {"discoverystream.config": JSON.stringify({enabled: false})}});
+      assert.notCalled(instance.handleDisabled);
+
+      instance.onAction({type: at.NEW_TAB_REHYDRATED, meta: {fromTarget: {}}});
+      assert.calledOnce(instance.getPocketState);
+    });
+    it("should handle UNINIT when discoverystream is enabled", async () => {
+      sinon.stub(instance, "uninit");
+      instance.onAction({type: at.UNINIT});
+      assert.calledOnce(instance.uninit);
+    });
+    it("should fire init on PREF_CHANGED", () => {
+      sinon.stub(instance, "onInit");
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.onInit);
+    });
+    it("should not fire init on PREF_CHANGED if stories are loaded", () => {
+      sinon.stub(instance, "onInit");
+      sinon.spy(instance, "lazyLoadTopStories");
+      instance.storiesLoaded = true;
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.lazyLoadTopStories);
+      assert.notCalled(instance.onInit);
+    });
+    it("should fire init on PREF_CHANGED when discoverystream is disabled", () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.stub(instance, "onInit");
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.onInit);
+    });
+    it("should not fire init on PREF_CHANGED when discoverystream is disabled and stories are loaded", () => {
+      instance.discoveryStreamEnabled = false;
+      sinon.stub(instance, "onInit");
+      sinon.spy(instance, "lazyLoadTopStories");
+      instance.storiesLoaded = true;
+      instance.onAction({type: at.PREF_CHANGED, data: {name: "discoverystream.config", value: {}}});
+      assert.calledOnce(instance.lazyLoadTopStories);
+      assert.notCalled(instance.onInit);
+    });
+  });
+
   describe("#init", () => {
     it("should create a TopStoriesFeed", () => {
       assert.instanceOf(instance, TopStoriesFeed);
     });
     it("should bind parseOptions to SectionsManager.onceInitialized", () => {
-      instance.onAction({type: at.INIT});
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {}});
       assert.calledOnce(sectionsManagerStub.onceInitialized);
     });
     it("should initialize endpoints based on options", async () => {
       await instance.onInit();
       assert.equal("https://somedomain.org/stories?key=test-api-key", instance.stories_endpoint);
       assert.equal("https://somedomain.org/referrer", instance.stories_referrer);
       assert.equal("https://somedomain.org/topics?key=test-api-key", instance.topics_endpoint);
     });
     it("should enable its section", () => {
-      instance.onAction({type: at.INIT});
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {}});
       assert.calledOnce(sectionsManagerStub.enableSection);
       assert.calledWith(sectionsManagerStub.enableSection, SECTION_ID);
     });
     it("init should fire onInit", () => {
       instance.onInit = sinon.spy();
-      instance.onAction({type: at.INIT});
+      instance.onAction({type: at.PREFS_INITIAL_VALUES, data: {}});
       assert.calledOnce(instance.onInit);
     });
     it("should fetch stories on init", async () => {
       instance.fetchStories = sinon.spy();
       await instance.onInit();
       assert.calledOnce(instance.fetchStories);
     });
     it("should fetch topics on init", async () => {