Bug 1588215 - Add modal-less welcome, send-tab recipes and bug fixes to New Tab Page r=Mardak,fluent-reviewers
authorPunam Dahiya <punamdahiya@yahoo.com>
Sat, 12 Oct 2019 07:01:16 +0000
changeset 497359 9d2586266fcd94d61cf0576c41b2f9b7351ae077
parent 497358 06ea2371f897a70c083d1dba53bd3930da2e997b
child 497360 b4e9b21cd886e4d22997ff24abf941d363f5bfb5
push id97833
push userelee@mozilla.com
push dateSat, 12 Oct 2019 07:02:54 +0000
treeherderautoland@9d2586266fcd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMardak, fluent-reviewers
bugs1588215
milestone71.0a1
Bug 1588215 - Add modal-less welcome, send-tab recipes and bug fixes to New Tab Page r=Mardak,fluent-reviewers Differential Revision: https://phabricator.services.mozilla.com/D49020
browser/components/newtab/.eslintrc.js
browser/components/newtab/common/Actions.jsm
browser/components/newtab/content-src/asrouter/asrouter-content.jsx
browser/components/newtab/content-src/asrouter/components/FxASignupForm/FxASignupForm.jsx
browser/components/newtab/content-src/asrouter/components/FxASignupForm/_FxASignupForm.scss
browser/components/newtab/content-src/asrouter/docs/debugging-docs.md
browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md
browser/components/newtab/content-src/asrouter/templates/FirstRun/FirstRun.jsx
browser/components/newtab/content-src/asrouter/templates/FirstRun/Interrupt.jsx
browser/components/newtab/content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt.jsx
browser/components/newtab/content-src/asrouter/templates/FullPageInterrupt/_FullPageInterrupt.scss
browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/ToolbarBadgeMessage.schema.json
browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
browser/components/newtab/content-src/asrouter/templates/Trailhead/_Trailhead.scss
browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSImage/DSImage.jsx
browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
browser/components/newtab/content-src/lib/link-menu-options.js
browser/components/newtab/content-src/styles/_activity-stream.scss
browser/components/newtab/content-src/styles/_variables.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/data/content/assets/trailhead/firefox-systems.png
browser/components/newtab/docs/v2-system-addon/data_events.md
browser/components/newtab/lib/ASRouter.jsm
browser/components/newtab/lib/ASRouterTargeting.jsm
browser/components/newtab/lib/ASRouterTriggerListeners.jsm
browser/components/newtab/lib/ActivityStream.jsm
browser/components/newtab/lib/DiscoveryStreamFeed.jsm
browser/components/newtab/lib/OnboardingMessageProvider.jsm
browser/components/newtab/lib/PanelTestProvider.jsm
browser/components/newtab/lib/PlacesFeed.jsm
browser/components/newtab/lib/ToolbarBadgeHub.jsm
browser/components/newtab/locales-src/asrouter.ftl
browser/components/newtab/locales-src/onboarding.ftl
browser/components/newtab/test/browser/browser_aboutwelcome.js
browser/components/newtab/test/browser/browser_asrouter_targeting.js
browser/components/newtab/test/browser/browser_asrouter_trigger_listeners.js
browser/components/newtab/test/browser/browser_onboarding_rtamo.js
browser/components/newtab/test/schemas/pings.js
browser/components/newtab/test/unit/asrouter/ASRouter.test.js
browser/components/newtab/test/unit/asrouter/ASRouterTriggerListeners.test.js
browser/components/newtab/test/unit/asrouter/PanelTestProvider.test.js
browser/components/newtab/test/unit/asrouter/templates/FullPageInterrupt.test.jsx
browser/components/newtab/test/unit/asrouter/templates/FxASignupForm.test.jsx
browser/components/newtab/test/unit/asrouter/templates/Interrupt.test.jsx
browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSDismiss.test.jsx
browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSImage.test.jsx
browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx
browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
browser/components/newtab/test/unit/lib/ToolbarBadgeHub.test.js
browser/components/newtab/test/unit/unit-entry.js
browser/locales/en-US/browser/newtab/asrouter.ftl
browser/locales/en-US/browser/newtab/onboarding.ftl
--- a/browser/components/newtab/.eslintrc.js
+++ b/browser/components/newtab/.eslintrc.js
@@ -40,16 +40,18 @@ module.exports = {
   },
   "overrides": [
     {
       // These files use fluent-dom to insert content
       "files": [
         "content-src/asrouter/templates/OnboardingMessage/**",
         "content-src/asrouter/templates/FirstRun/**",
         "content-src/asrouter/templates/Trailhead/**",
+        "content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt.jsx",
+        "content-src/asrouter/components/FxASignupForm/FxASignupForm.jsx",
         "content-src/components/TopSites/**",
         "content-src/components/MoreRecommendations/MoreRecommendations.jsx",
         "content-src/components/CollapsibleSection/CollapsibleSection.jsx",
         "content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx",
         "content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx"
       ],
       "rules": {
         "jsx-a11y/anchor-has-content": 0,
--- a/browser/components/newtab/common/Actions.jsm
+++ b/browser/components/newtab/common/Actions.jsm
@@ -118,16 +118,17 @@ for (const type of [
   "SKIPPED_SIGNIN",
   "SNIPPETS_BLOCKLIST_CLEARED",
   "SNIPPETS_BLOCKLIST_UPDATED",
   "SNIPPETS_DATA",
   "SNIPPETS_PREVIEW_MODE",
   "SNIPPETS_RESET",
   "SNIPPET_BLOCKED",
   "SUBMIT_EMAIL",
+  "SUBMIT_SIGNIN",
   "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",
@@ -149,16 +150,17 @@ for (const type of [
 ]) {
   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 [
+  "HIGHLIGHT_FEATURE",
   "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",
--- a/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
+++ b/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
@@ -16,16 +16,17 @@ import React from "react";
 import ReactDOM from "react-dom";
 import { SnippetsTemplates } from "./templates/template-manifest";
 import { FirstRun } from "./templates/FirstRun/FirstRun";
 
 const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
 const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
 const TEMPLATES_ABOVE_PAGE = [
   "trailhead",
+  "full_page_interrupt",
   "return_to_amo_overlay",
   "extended_triplets",
 ];
 const FIRST_RUN_TEMPLATES = TEMPLATES_ABOVE_PAGE;
 const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
 
 export const ASRouterUtils = {
   addListener(listener) {
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/components/FxASignupForm/FxASignupForm.jsx
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { actionCreators as ac } from "common/Actions.jsm";
+import { addUtmParams } from "../../templates/FirstRun/addUtmParams";
+import React from "react";
+
+export class FxASignupForm extends React.PureComponent {
+  constructor(props) {
+    super(props);
+    this.onSubmit = this.onSubmit.bind(this);
+    this.onInputChange = this.onInputChange.bind(this);
+    this.onInputInvalid = this.onInputInvalid.bind(this);
+    this.handleSignIn = this.handleSignIn.bind(this);
+
+    this.state = {
+      emailInput: "",
+    };
+  }
+
+  get email() {
+    return this.props.document
+      .getElementById("fxaSignupForm")
+      .querySelector("input[name=email]");
+  }
+
+  onSubmit(event) {
+    let userEvent = "SUBMIT_EMAIL";
+    const { email } = event.target.elements;
+    if (email.disabled) {
+      userEvent = "SUBMIT_SIGNIN";
+    } else if (!email.value.length) {
+      email.required = true;
+      email.checkValidity();
+      event.preventDefault();
+      return;
+    }
+
+    // Report to telemetry additional information about the form submission.
+    const value = { has_flow_params: !!this.props.flowParams.flowId.length };
+    this.props.dispatch(ac.UserEvent({ event: userEvent, value }));
+
+    global.addEventListener("visibilitychange", this.props.onClose);
+  }
+
+  handleSignIn(event) {
+    // Set disabled to prevent email from appearing in url resulting in the wrong page
+    this.email.disabled = true;
+  }
+
+  componentDidMount() {
+    // Start with focus in the email input box
+    if (this.email) {
+      this.email.focus();
+    }
+  }
+
+  onInputChange(e) {
+    let error = e.target.previousSibling;
+    this.setState({ emailInput: e.target.value });
+    error.classList.remove("active");
+    e.target.classList.remove("invalid");
+  }
+
+  onInputInvalid(e) {
+    let error = e.target.previousSibling;
+    error.classList.add("active");
+    e.target.classList.add("invalid");
+    e.preventDefault(); // Override built-in form validation popup
+    e.target.focus();
+  }
+
+  render() {
+    const { content, UTMTerm } = this.props;
+    return (
+      <div
+        id="fxaSignupForm"
+        role="group"
+        aria-labelledby="joinFormHeader"
+        aria-describedby="joinFormBody"
+        className="fxaSignupForm"
+      >
+        <h3 id="joinFormHeader" data-l10n-id={content.form.title.string_id} />
+        <p id="joinFormBody" data-l10n-id={content.form.text.string_id} />
+        <form
+          method="get"
+          action={this.props.fxaEndpoint}
+          target="_blank"
+          rel="noopener noreferrer"
+          onSubmit={this.onSubmit}
+        >
+          <input name="service" type="hidden" value="sync" />
+          <input name="action" type="hidden" value="email" />
+          <input name="context" type="hidden" value="fx_desktop_v3" />
+          <input
+            name="entrypoint"
+            type="hidden"
+            value="activity-stream-firstrun"
+          />
+          <input name="utm_source" type="hidden" value="activity-stream" />
+          <input name="utm_campaign" type="hidden" value="firstrun" />
+          <input name="utm_term" type="hidden" value={UTMTerm} />
+          <input
+            name="device_id"
+            type="hidden"
+            value={this.props.flowParams.deviceId}
+          />
+          <input
+            name="flow_id"
+            type="hidden"
+            value={this.props.flowParams.flowId}
+          />
+          <input
+            name="flow_begin_time"
+            type="hidden"
+            value={this.props.flowParams.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}
+            name="email"
+            type="email"
+            onInvalid={this.onInputInvalid}
+            onChange={this.onInputChange}
+          />
+          <p className="fxa-terms" data-l10n-id="onboarding-join-form-legal">
+            <a
+              data-l10n-name="terms"
+              target="_blank"
+              rel="noopener noreferrer"
+              href={addUtmParams(
+                "https://accounts.firefox.com/legal/terms",
+                UTMTerm
+              )}
+            />
+            <a
+              data-l10n-name="privacy"
+              target="_blank"
+              rel="noopener noreferrer"
+              href={addUtmParams(
+                "https://accounts.firefox.com/legal/privacy",
+                UTMTerm
+              )}
+            />
+          </p>
+          <button data-l10n-id={content.form.button.string_id} type="submit" />
+          {this.props.showSignInLink && (
+            <div className="fxa-signin">
+              <span data-l10n-id="onboarding-join-form-signin-label" />
+              <button
+                data-l10n-id="onboarding-join-form-signin"
+                onClick={this.handleSignIn}
+              />
+            </div>
+          )}
+        </form>
+      </div>
+    );
+  }
+}
+
+FxASignupForm.defaultProps = { document: global.document };
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/components/FxASignupForm/_FxASignupForm.scss
@@ -0,0 +1,126 @@
+.fxaSignupForm {
+  min-width: 260px;
+  text-align: center;
+
+  a {
+    color: $white;
+    text-decoration: underline;
+  }
+
+  input,
+  button {
+    border-radius: 4px;
+    padding: 10px;
+  }
+
+  h3 {
+    font-size: 36px;
+    font-weight: 200;
+    line-height: 46px;
+    margin: 12px 0 4px;
+  }
+
+  p {
+    font-size: 15px;
+    line-height: 22px;
+    margin: 0 0 20px;
+  }
+
+  .fxa-terms {
+    margin: 4px 30px 20px;
+
+    a,
+    & {
+      color: $white-70;
+      font-size: 12px;
+      line-height: 20px;
+    }
+  }
+
+  .fxa-signin {
+    font-size: 16px;
+    margin-top: 19px;
+
+    span {
+      margin-inline-end: 5px;
+    }
+
+    button {
+      background-color: initial;
+      text-decoration: underline;
+      color: $white;
+      display: inline;
+      padding: 0;
+      width: auto;
+
+      &:hover,
+      &:focus,
+      &:active {
+        background-color: initial;
+      }
+    }
+  }
+
+  form {
+    position: relative;
+
+    .error.active {
+      inset-inline-start: 0;
+      z-index: 0;
+    }
+  }
+
+  button,
+  input {
+    width: 100%;
+  }
+
+  input {
+    background-color: $white;
+    border: 1px solid $grey-50;
+    box-shadow: none;
+    color: $grey-70;
+    font-size: 15px;
+    transition: border-color 150ms, box-shadow 150ms;
+
+    &:hover {
+      border-color: $grey-90;
+    }
+
+    &:focus {
+      border-color: $blue-50;
+      box-shadow: 0 0 0 3px $email-input-focus;
+    }
+
+    &.invalid {
+      border-color: $red-60;
+    }
+
+    &.invalid:focus {
+      box-shadow: 0 0 0 3px $email-input-invalid;
+    }
+  }
+
+  button {
+    background-color: $blue-60;
+    border: 0;
+    cursor: pointer;
+    display: block;
+    font-size: 15px;
+    font-weight: 400;
+    padding: 14px;
+
+    &:hover,
+    &:focus {
+      background-color: $trailhead-blue-60;
+    }
+
+    &:focus {
+      outline: dotted 1px;
+    }
+
+    &:active {
+      background-color: $trailhead-blue-70;
+    }
+  }
+}
--- a/browser/components/newtab/content-src/asrouter/docs/debugging-docs.md
+++ b/browser/components/newtab/content-src/asrouter/docs/debugging-docs.md
@@ -24,19 +24,19 @@ The messages on the page should now be f
 
 ## How to test telemetry pings
 
 To test telemetry pings, complete the the following steps:
 
 - In about:config, set:
   - `browser.newtabpage.activity-stream.telemetry` to `true`
   - `browser.ping-centre.log` to `true`
-- Open the Browser Toolbox devtools (Tools > Developer > Browser Toolbox) and switch to the console tab. Add a filter for for `activity-stream-router` to only display relevant pings:
+- Open the Browser Toolbox devtools (Tools > Web Developer > Browser Toolbox) and switch to the console tab. Add a filter for for `activity-stream` to only display relevant pings:
 
-![Devtools telemetry pong](./telemetry-screenshot.png)
+![Devtools telemetry ping](./telemetry-screenshot.png)
 
 You should now see pings show up as you view/interact with ASR messages/templates.
 
 ## Snippets debugging
 
 ### How to view preview URLs
 
 Follow these steps to view preview URLs (e.g. `about:newtab?endpoint=https://gist.githubusercontent.com/piatra/d193ca7e0f513cc19fc6a1d396c214f7/raw/8bcaf9548212e4c613577e839198cc14e7317630/newsletter_snippet.json&theme=dark`)
--- a/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md
+++ b/browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md
@@ -35,16 +35,18 @@ Please note that some targeting attribut
 * [xpinstallEnabled](#xpinstallEnabled)
 * [hasPinnedTabs](#haspinnedtabs)
 * [hasAccessedFxAPanel](#hasaccessedfxapanel)
 * [isWhatsNewPanelEnabled](#iswhatsnewpanelenabled)
 * [isFxABadgeEnabled](#isfxabadgeenabled)
 * [totalBlockedCount](#totalblockedcount)
 * [recentBookmarks](#recentbookmarks)
 * [userPrefs](#userprefs)
+* [attachedFxAOAuthClients](#attachedfxaoauthclients)
+* [platformName](#platformname)
 
 ## Detailed usage
 
 ### `addonsInfo`
 Provides information about the add-ons the user has installed.
 
 Note that the `name`, `userDisabled`, and `installDate` is only available if `isFullData` is `true` (this is usually not the case right at start-up).
 
@@ -550,8 +552,44 @@ userPrefs.cfrFeatures == false
 
 ```ts
 declare const userPrefs: {
   cfrFeatures: boolean;
   cfrAddons: boolean;
   snippets: boolean;
 }
 ```
+
+### `attachedFxAOAuthClients`
+
+Information about connected services associated with the FxA Account.
+
+#### Definition
+
+```
+interface OAuthClient {
+  id: string;
+  // FxA service name
+  name: string;
+  lastAccessTime: UnixEpochNumber;
+}
+
+declare const attachedFxAOAuthClients: Array<OAuthClient>
+```
+
+#### Examples
+```javascript
+{
+  id: "7377719276ad44ee",
+  name: "Pocket",
+  lastAccessTime: 1513599164000
+}
+```
+
+### `platformName`
+
+[Platform information](https://searchfox.org/mozilla-central/rev/05a22d864814cb1e4352faa4004e1f975c7d2eb9/toolkit/modules/AppConstants.jsm#156).
+
+#### Definition
+
+```
+declare const platformName = "linux" | "win" | "macosx" | "android" | "other";
+```
--- a/browser/components/newtab/content-src/asrouter/templates/FirstRun/FirstRun.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/FirstRun/FirstRun.jsx
@@ -175,16 +175,17 @@ export class FirstRun extends React.Pure
       flowParams,
     } = this.state;
 
     return (
       <>
         {isInterruptVisible ? (
           <Interrupt
             document={props.document}
+            cards={triplets}
             message={interrupt}
             onNextScene={this.closeInterrupt}
             UTMTerm={UTMTerm}
             sendUserActionTelemetry={sendUserActionTelemetry}
             executeAction={executeAction}
             dispatch={dispatch}
             flowParams={flowParams}
             onDismiss={this.closeInterrupt}
--- a/browser/components/newtab/content-src/asrouter/templates/FirstRun/Interrupt.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/FirstRun/Interrupt.jsx
@@ -1,21 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import React from "react";
 import { Trailhead } from "../Trailhead/Trailhead";
 import { ReturnToAMO } from "../ReturnToAMO/ReturnToAMO";
+import { FullPageInterrupt } from "../FullPageInterrupt/FullPageInterrupt";
 import { LocalizationProvider } from "fluent-react";
 import { generateBundles } from "../../rich-text-strings";
 
 export class Interrupt extends React.PureComponent {
   render() {
     const {
+      cards,
       onDismiss,
       onNextScene,
       message,
       sendUserActionTelemetry,
       executeAction,
       dispatch,
       fxaEndpoint,
       UTMTerm,
@@ -33,16 +35,31 @@ export class Interrupt extends React.Pur
               document={this.props.document}
               UISurface="NEWTAB_OVERLAY"
               onBlock={onDismiss}
               onAction={executeAction}
               sendUserActionTelemetry={sendUserActionTelemetry}
             />
           </LocalizationProvider>
         );
+      case "full_page_interrupt":
+        return (
+          <FullPageInterrupt
+            document={this.props.document}
+            cards={cards}
+            message={message}
+            onBlock={onDismiss}
+            onAction={executeAction}
+            dispatch={dispatch}
+            fxaEndpoint={fxaEndpoint}
+            sendUserActionTelemetry={sendUserActionTelemetry}
+            UTMTerm={UTMTerm}
+            flowParams={flowParams}
+          />
+        );
       case "trailhead":
         return (
           <Trailhead
             document={this.props.document}
             message={message}
             onNextScene={onNextScene}
             onAction={executeAction}
             sendUserActionTelemetry={sendUserActionTelemetry}
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt.jsx
@@ -0,0 +1,184 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { addUtmParams } from "../FirstRun/addUtmParams";
+import { FxASignupForm } from "../../components/FxASignupForm/FxASignupForm";
+import { OnboardingCard } from "../../templates/OnboardingMessage/OnboardingMessage";
+import React from "react";
+
+export const FxAccounts = ({
+  document,
+  content,
+  dispatch,
+  fxaEndpoint,
+  flowParams,
+  removeOverlay,
+  url,
+  UTMTerm,
+}) => (
+  <React.Fragment>
+    <div
+      className="fullpage-left-section"
+      aria-labelledby="fullpage-left-title"
+      aria-describedby="fullpage-left-content"
+    >
+      <h1
+        id="fullpage-left-title"
+        className="fullpage-left-title"
+        data-l10n-id="onboarding-welcome-body"
+      />
+      <p
+        id="fullpage-left-content"
+        className="fullpage-left-content"
+        data-l10n-id="onboarding-benefit-products-text"
+      />
+      <p
+        className="fullpage-left-content"
+        data-l10n-id="onboarding-benefit-privacy-text"
+      />
+      <a
+        className="fullpage-left-link"
+        href={addUtmParams(url, UTMTerm)}
+        target="_blank"
+        rel="noopener noreferrer"
+        data-l10n-id="onboarding-welcome-learn-more"
+      />
+      <div className="fullpage-icon fx-systems-icons" />
+    </div>
+    <div className="fullpage-form">
+      <FxASignupForm
+        document={document}
+        content={content}
+        dispatch={dispatch}
+        fxaEndpoint={fxaEndpoint}
+        UTMTerm={UTMTerm}
+        flowParams={flowParams}
+        onClose={removeOverlay}
+        showSignInLink={true}
+      />
+    </div>
+  </React.Fragment>
+);
+
+export const FxCards = ({ cards, onCardAction, sendUserActionTelemetry }) => (
+  <React.Fragment>
+    {cards.map(card => (
+      <OnboardingCard
+        key={card.id}
+        className="trailheadCard"
+        sendUserActionTelemetry={sendUserActionTelemetry}
+        onAction={onCardAction}
+        UISurface="TRAILHEAD"
+        {...card}
+      />
+    ))}
+  </React.Fragment>
+);
+
+export class FullPageInterrupt extends React.PureComponent {
+  constructor(props) {
+    super(props);
+    this.removeOverlay = this.removeOverlay.bind(this);
+    this.onCardAction = this.onCardAction.bind(this);
+  }
+
+  componentWillMount() {
+    global.document.body.classList.add("trailhead-fullpage");
+  }
+
+  componentDidMount() {
+    // Hide the page content from screen readers while the full page interrupt is open
+    this.props.document
+      .getElementById("root")
+      .setAttribute("aria-hidden", "true");
+  }
+
+  removeOverlay() {
+    window.removeEventListener("visibilitychange", this.removeOverlay);
+    document.body.classList.remove("hide-main", "trailhead-fullpage");
+    // Re-enable the document for screen readers
+    this.props.document
+      .getElementById("root")
+      .setAttribute("aria-hidden", "false");
+
+    this.props.onBlock();
+    document.body.classList.remove("welcome");
+  }
+
+  onCardAction(action) {
+    let actionUpdates = {};
+    const { flowParams, UTMTerm } = this.props;
+
+    if (action.type === "OPEN_URL") {
+      let url = new URL(action.data.args);
+      addUtmParams(url, UTMTerm);
+
+      if (action.addFlowParams) {
+        url.searchParams.append("device_id", flowParams.deviceId);
+        url.searchParams.append("flow_id", flowParams.flowId);
+        url.searchParams.append("flow_begin_time", flowParams.flowBeginTime);
+      }
+
+      actionUpdates = { data: { ...action.data, args: url.toString() } };
+    }
+
+    this.props.onAction({ ...action, ...actionUpdates });
+    this.removeOverlay();
+  }
+
+  render() {
+    const { props } = this;
+    const { content } = props.message;
+    const cards = (
+      <FxCards
+        cards={props.cards}
+        onCardAction={this.onCardAction}
+        sendUserActionTelemetry={props.sendUserActionTelemetry}
+      />
+    );
+
+    const accounts = (
+      <FxAccounts
+        document={props.document}
+        content={content}
+        dispatch={props.dispatch}
+        fxaEndpoint={props.fxaEndpoint}
+        flowParams={props.flowParams}
+        removeOverlay={this.removeOverlay}
+        url={content.learn.url}
+        UTMTerm={props.UTMTerm}
+      />
+    );
+
+    // By default we show accounts section on top and
+    // cards section in bottom half of the full page interrupt
+    const cardsFirst = content && content.className === "fullPageCardsAtTop";
+    const firstContainerClassName = [
+      "container",
+      content && content.className,
+    ].join(" ");
+    return (
+      <div className="fullpage-wrapper">
+        <div className="fullpage-icon brand-logo" />
+        <h1
+          className="welcome-title"
+          data-l10n-id="onboarding-welcome-header"
+        />
+        <h2
+          className="welcome-subtitle"
+          data-l10n-id="onboarding-fullpage-welcome-subheader"
+        />
+        <div className={firstContainerClassName}>
+          {cardsFirst ? cards : accounts}
+        </div>
+        <div className="section-divider" />
+        <div className="container">{cardsFirst ? accounts : cards}</div>
+      </div>
+    );
+  }
+}
+
+FullPageInterrupt.defaultProps = {
+  flowParams: { deviceId: "", flowId: "", flowBeginTime: "" },
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/content-src/asrouter/templates/FullPageInterrupt/_FullPageInterrupt.scss
@@ -0,0 +1,259 @@
+.activity-stream {
+  &.welcome {
+    overflow: hidden;
+  }
+
+  &:not(.welcome) {
+    .fullpage-wrapper {
+      display: none;
+    }
+  }
+}
+
+.fullpage-wrapper {
+  $responsive-breakpoint: 850px;
+  $responsive-width: 352px;
+  $header-size: 36px;
+  $form-text-size: 16px;
+
+  align-content: center;
+  display: flex;
+  flex-direction: column;
+  overflow-x: auto;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 21000;
+  background-color: $ghost-white;
+
+  + div {
+    opacity: 0;
+  }
+
+  .fullpage-icon {
+    background-position-x: left;
+    background-repeat: no-repeat;
+    background-size: contain;
+
+    &:dir(rtl) {
+      background-position-x: right;
+    }
+
+    @media screen and (max-width: $responsive-breakpoint) {
+      background-position: center;
+    }
+  }
+
+  .brand-logo {
+    background-image: url('chrome://branding/content/about-logo.png');
+    margin: 20px 10px 10px 20px;
+    padding-bottom: 50px;
+  }
+
+  .welcome-title,
+  .welcome-subtitle {
+    align-self: center;
+    margin: 0;
+    @media screen and (max-width: $responsive-breakpoint) {
+      text-align: center;
+    }
+  }
+
+  .welcome-title {
+    color: $trailhead-purple-80;
+    font-size: 46px;
+    font-weight: 600;
+    line-height: 62px;
+  }
+
+  .welcome-subtitle {
+    color: $trailhead-violet;
+    font-size: 20px;
+    line-height: 27px;
+  }
+
+  .container {
+    display: flex;
+    align-self: center;
+    padding: 50px 0;
+
+    @media screen and (max-width: $responsive-breakpoint) {
+      flex-direction: column;
+      width: $responsive-width;
+      text-align: center;
+    }
+  }
+
+  .fullpage-left-section {
+    position: relative;
+    width: 538px;
+    font-size: 18px;
+    line-height: 30px;
+
+    @media screen and (max-width: $responsive-breakpoint) {
+      width: $responsive-width;
+    }
+
+    .fullpage-left-content {
+      color: $grey-60;
+      display: inline;
+      margin: 0;
+      margin-inline-end: 2px;
+    }
+
+    .fullpage-left-link {
+      color: $blue-60;
+      display: block;
+      text-decoration: underline;
+      margin-bottom: 30px;
+
+      &:hover,
+      &:active,
+      &:focus {
+        color: $blue-60;
+      }
+    }
+
+    .fullpage-left-title {
+      margin: 0;
+      color: $trailhead-purple-80;
+      font-size: $header-size;
+      line-height: 48px;
+    }
+
+    .fx-systems-icons {
+      height: 33px;
+      display: block;
+      background-image: url('#{$image-path}trailhead/firefox-systems.png');
+      margin-bottom: 20px;
+    }
+  }
+
+  .fullpage-form {
+    position: relative;
+    text-align: center;
+    margin-inline-start: $header-size;
+
+    @media screen and (max-width: $responsive-breakpoint) {
+      margin-inline-start: 0;
+    }
+
+    .fxaSignupForm {
+      width: 356px;
+      padding: 25px;
+      box-shadow: 0 0 16px 0 $black-15;
+      border-radius: 6px;
+      background: $white;
+    }
+
+    .fxa-terms {
+      margin: 4px 0 20px;
+
+      a,
+      & {
+        color: $grey-60;
+        font-size: 12px;
+        line-height: $form-text-size;
+      }
+    }
+
+    .fxa-signin {
+      color: $grey-60;
+      line-height: 30px;
+      opacity: 0.77;
+
+      button {
+        color: $blue-60;
+      }
+    }
+
+    h3 {
+      color: $trailhead-purple-80;
+      font-weight: 400;
+      font-size: $header-size;
+      line-height: $header-size;
+      margin: 0;
+      padding: 8px;
+    }
+
+    h3 + p {
+      color: $grey-60;
+      font-size: $form-text-size;
+      line-height: 20px;
+      opacity: 0.77;
+    }
+
+    input {
+      background: $white;
+      border: 1px solid $grey-30;
+      border-radius: 2px;
+
+      &:hover {
+        border-color: $grey-50;
+      }
+
+      &.invalid {
+        border-color: $red-60;
+      }
+    }
+
+    button {
+      color: $white;
+      font-size: $form-text-size;
+
+      &:focus {
+        outline: dotted 1px $grey-50;
+      }
+    }
+  }
+
+  .section-divider::after {
+    content: '';
+    display: block;
+    border-bottom: 0.5px solid $grey-30;
+  }
+
+  .trailheadCard {
+    box-shadow: none;
+    background: none;
+    text-align: center;
+    width: 320px;
+    padding: 18px;
+
+    .onboardingTitle {
+      color: $grey-90;
+    }
+
+    .onboardingText {
+      font-weight: normal;
+      color: $grey-60;
+      margin-top: 4px;
+    }
+
+    .onboardingButton {
+      color: $grey-60;
+      background: $grey-90-10;
+
+      &:focus,
+      &:hover {
+        background: $grey-90-20;
+      }
+
+      &:active {
+        background: $grey-90-30;
+      }
+    }
+
+    .onboardingMessageImage {
+      height: 112px;
+      width: 154px;
+      background-size: 154px;
+    }
+
+    @media screen and (max-width: $responsive-breakpoint) {
+      width: $responsive-width;
+    }
+  }
+}
--- a/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/ToolbarBadgeMessage.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/ToolbarBadgeMessage.schema.json
@@ -1,12 +1,12 @@
 {
   "title": "ToolbarBadgeMessage",
   "description": "A template that specifies to which element in the browser toolbar to add a notification.",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "type": "object",
   "properties": {
     "target": {
       "type": "string"
     },
     "action": {
       "type": "object",
       "properties": {
@@ -16,13 +16,24 @@
       },
       "additionalProperties": false,
       "required": ["id"],
       "description": "Optional action to take in addition to showing the notification"
     },
     "delay": {
       "type": "number",
       "description": "Optional delay in ms after which to show the notification"
+    },
+    "badgeDescription": {
+      "type": "object",
+      "description": "This is used in combination with the badged button to offer a text based alternative to the visual badging. Example 'New Feature: What's New'",
+      "properties": {
+        "string_id": {
+          "type": "string",
+          "description": "Fluent string id"
+        }
+      },
+      "required": ["string_id"]
     }
   },
   "additionalProperties": false,
   "required": ["target"]
 }
--- a/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
+++ b/browser/components/newtab/content-src/asrouter/templates/Trailhead/Trailhead.jsx
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import { actionCreators as ac } from "common/Actions.jsm";
 import { ModalOverlayWrapper } from "../../components/ModalOverlay/ModalOverlay";
+import { FxASignupForm } from "../../components/FxASignupForm/FxASignupForm";
 import { addUtmParams } from "../FirstRun/addUtmParams";
 import React from "react";
 
 // From resource://devtools/client/shared/focus.js
 const FOCUSABLE_SELECTOR = [
   "a[href]:not([tabindex='-1'])",
   "button:not([disabled]):not([tabindex='-1'])",
   "iframe:not([tabindex='-1'])",
@@ -17,84 +18,47 @@ const FOCUSABLE_SELECTOR = [
   "textarea:not([disabled]):not([tabindex='-1'])",
   "[tabindex]:not([tabindex='-1'])",
 ].join(", ");
 
 export class Trailhead extends React.PureComponent {
   constructor(props) {
     super(props);
     this.closeModal = this.closeModal.bind(this);
-    this.onInputChange = this.onInputChange.bind(this);
     this.onStartBlur = this.onStartBlur.bind(this);
-    this.onSubmit = this.onSubmit.bind(this);
-    this.onInputInvalid = this.onInputInvalid.bind(this);
-
-    this.state = {
-      emailInput: "",
-    };
   }
 
   get dialog() {
     return this.props.document.getElementById("trailheadDialog");
   }
 
   componentDidMount() {
     // We need to remove hide-main since we should show it underneath everything that has rendered
     this.props.document.body.classList.remove("hide-main");
 
     // The rest of the page is "hidden" to screen readers when the modal is open
     this.props.document
       .getElementById("root")
       .setAttribute("aria-hidden", "true");
-    // Start with focus in the email input box
-    const input = this.dialog.querySelector("input[name=email]");
-    if (input) {
-      input.focus();
-    }
-  }
-
-  onInputChange(e) {
-    let error = e.target.previousSibling;
-    this.setState({ emailInput: e.target.value });
-    error.classList.remove("active");
-    e.target.classList.remove("invalid");
   }
 
   onStartBlur(event) {
     // Make sure focus stays within the dialog when tabbing from the button
     const { dialog } = this;
     if (
       event.relatedTarget &&
       !(
         dialog.compareDocumentPosition(event.relatedTarget) &
         dialog.DOCUMENT_POSITION_CONTAINED_BY
       )
     ) {
       dialog.querySelector(FOCUSABLE_SELECTOR).focus();
     }
   }
 
-  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");
     this.props.document.getElementById("root").removeAttribute("aria-hidden");
     this.props.onNextScene();
 
     // If closeModal() was triggered by a visibilitychange event, the user actually
     // submitted the email form so we don't send a SKIPPED_SIGNIN ping.
@@ -111,24 +75,16 @@ export class Trailhead extends React.Pur
   /**
    * Report to telemetry additional information about the form submission.
    */
   _getFormInfo() {
     const value = { has_flow_params: !!this.props.flowParams.flowId.length };
     return { value };
   }
 
-  onInputInvalid(e) {
-    let error = e.target.previousSibling;
-    error.classList.add("active");
-    e.target.classList.add("invalid");
-    e.preventDefault(); // Override built-in form validation popup
-    e.target.focus();
-  }
-
   render() {
     const { props } = this;
     const { UTMTerm } = props;
     const { content } = props.message;
     const innerClassName = ["trailhead", content && content.className]
       .filter(v => v)
       .join(" ");
 
@@ -157,100 +113,26 @@ export class Trailhead extends React.Pur
             <a
               className="trailheadLearn"
               data-l10n-id={content.learn.text.string_id}
               href={addUtmParams(content.learn.url, UTMTerm)}
               target="_blank"
               rel="noopener noreferrer"
             />
           </div>
-          <div
-            role="group"
-            aria-labelledby="joinFormHeader"
-            aria-describedby="joinFormBody"
-            className="trailheadForm"
-          >
-            <h3
-              id="joinFormHeader"
-              data-l10n-id={content.form.title.string_id}
+          <div className="trailhead-join-form">
+            <FxASignupForm
+              document={this.props.document}
+              content={content}
+              dispatch={this.props.dispatch}
+              fxaEndpoint={this.props.fxaEndpoint}
+              UTMTerm={UTMTerm}
+              flowParams={this.props.flowParams}
+              onClose={this.closeModal}
             />
-            <p id="joinFormBody" data-l10n-id={content.form.text.string_id} />
-            <form
-              method="get"
-              action={this.props.fxaEndpoint}
-              target="_blank"
-              rel="noopener noreferrer"
-              onSubmit={this.onSubmit}
-            >
-              <input name="service" type="hidden" value="sync" />
-              <input name="action" type="hidden" value="email" />
-              <input name="context" type="hidden" value="fx_desktop_v3" />
-              <input
-                name="entrypoint"
-                type="hidden"
-                value="activity-stream-firstrun"
-              />
-              <input name="utm_source" type="hidden" value="activity-stream" />
-              <input name="utm_campaign" type="hidden" value="firstrun" />
-              <input name="utm_term" type="hidden" value={UTMTerm} />
-              <input
-                name="device_id"
-                type="hidden"
-                value={this.props.flowParams.deviceId}
-              />
-              <input
-                name="flow_id"
-                type="hidden"
-                value={this.props.flowParams.flowId}
-              />
-              <input
-                name="flow_begin_time"
-                type="hidden"
-                value={this.props.flowParams.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}
-                name="email"
-                type="email"
-                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={addUtmParams(
-                    "https://accounts.firefox.com/legal/terms",
-                    UTMTerm
-                  )}
-                />
-                <a
-                  data-l10n-name="privacy"
-                  target="_blank"
-                  rel="noopener noreferrer"
-                  href={addUtmParams(
-                    "https://accounts.firefox.com/legal/privacy",
-                    UTMTerm
-                  )}
-                />
-              </p>
-              <button
-                data-l10n-id={content.form.button.string_id}
-                type="submit"
-              />
-            </form>
           </div>
         </div>
 
         <button
           className="trailheadStart"
           data-l10n-id={content.skipButton.string_id}
           onBlur={this.onStartBlur}
           onClick={this.closeModal}
--- a/browser/components/newtab/content-src/asrouter/templates/Trailhead/_Trailhead.scss
+++ b/browser/components/newtab/content-src/asrouter/templates/Trailhead/_Trailhead.scss
@@ -1,14 +1,15 @@
 .trailhead {
   $benefit-icon-size: 62px;
   $benefit-icon-spacing: $benefit-icon-size + 12px;
   $benefit-icon-size-small: 40px;
   $benefit-icon-spacing-small: $benefit-icon-size-small + 12px;
   $responsive-breakpoint: 850px;
+  $logo-size: 100px;
 
   background: url('#{$image-path}trailhead/accounts-form-bg.jpg') bottom / cover;
   color: $white;
   height: auto;
 
   a {
     color: $white;
     text-decoration: underline;
@@ -42,16 +43,23 @@
       margin-top: 30px;
 
       @media (min-width: $responsive-breakpoint) {
         margin-inline-start: $benefit-icon-spacing;
       }
     }
   }
 
+  .trailhead-join-form {
+    background: url('#{$image-path}trailhead/firefox-logo.png') top center / $logo-size no-repeat;
+    color: $white;
+    min-width: 260px;
+    padding-top: $logo-size;
+  }
+
   &.syncCohort {
     left: calc(50% - 430px);
     width: 860px;
 
     @media (max-width: 860px) {
       left: 0;
       width: 100%;
     }
@@ -136,113 +144,16 @@
     p {
       color: $white;
       font-size: 15px;
       line-height: 22px;
       margin: 4px 0 15px;
     }
   }
 
-  .trailheadForm {
-    $logo-size: 100px;
-
-    background: url('#{$image-path}trailhead/firefox-logo.png') top center / $logo-size no-repeat;
-    min-width: 260px;
-    padding-top: $logo-size;
-    text-align: center;
-
-    h3 {
-      font-size: 36px;
-      font-weight: 200;
-      line-height: 46px;
-      margin: 12px 0 4px;
-    }
-
-    p {
-      color: $white;
-      font-size: 15px;
-      line-height: 22px;
-      margin: 0 0 20px;
-    }
-
-    .trailheadTerms {
-      margin: 4px 30px 20px;
-
-      a,
-      & {
-        color: $white-70;
-        font-size: 12px;
-        line-height: 20px;
-      }
-    }
-
-    form {
-      position: relative;
-
-      .error.active {
-        inset-inline-start: 0;
-        z-index: 0;
-      }
-    }
-
-    button,
-    input {
-      width: 100%;
-    }
-
-    input {
-      background-color: $white;
-      border: 1px solid $grey-50;
-      box-shadow: none;
-      color: $grey-70;
-      font-size: 15px;
-      transition: border-color 150ms, box-shadow 150ms;
-
-      &:hover {
-        border-color: $grey-90;
-      }
-
-      &:focus {
-        border-color: $blue-50;
-        box-shadow: 0 0 0 3px $email-input-focus;
-      }
-
-      &.invalid {
-        border-color: $red-60;
-      }
-
-      &.invalid:focus {
-        box-shadow: 0 0 0 3px $email-input-invalid;
-      }
-    }
-
-    button {
-      background-color: $blue-60;
-      border: 0;
-      cursor: pointer;
-      display: block;
-      font-size: 15px;
-      font-weight: 400;
-      padding: 14px;
-
-      &:hover,
-      &:focus {
-        background-color: $trailhead-blue-60;
-      }
-
-      &:focus {
-        outline: dotted 1px;
-      }
-
-      &:active {
-        background-color: $trailhead-blue-70;
-      }
-    }
-  }
-
   .trailheadStart {
     border: 1px solid $white-50;
     cursor: pointer;
     display: block;
     font-size: 15px;
     font-weight: 400;
     margin: 0 auto 40px;
     min-width: 300px;
@@ -479,24 +390,8 @@
     transform: translateY(-15px);
   }
 
   100% {
     opacity: 1;
     transform: translateY(0);
   }
 }
-
-.firstrun-title {
-  background: url('chrome://branding/content/about-logo.png') top left no-repeat;
-  background-size: 90px 90px;
-  margin: 40px 0 10px;
-  padding-top: 110px;
-
-  @media screen and (max-width: 790px) {
-    background: url('chrome://branding/content/about-logo.png') top center no-repeat;
-    background-size: 90px 90px;
-  }
-
-  &:dir(rtl) {
-    background-position: top right;
-  }
-}
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
@@ -233,16 +233,17 @@ export class DSCard extends React.PureCo
           dispatch={this.props.dispatch}
           url={this.props.url}
           title={this.props.title}
           source={this.props.source}
           type={this.props.type}
           pocket_id={this.props.pocket_id}
           shim={this.props.shim}
           bookmarkGuid={this.props.bookmarkGuid}
+          campaignId={this.props.campaignId}
         />
       </div>
     );
   }
 }
 
 DSCard.defaultProps = {
   windowObj: window, // Added to support unit tests
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss
@@ -120,16 +120,17 @@
         background: $grey-90-30;
       }
       width: 100%;
       margin: 12px 0 4px;
       box-shadow: none;
       border-radius: 4px;
       height: 32px;
       font-size: 14px;
+      font-weight: 600;
       padding: 5px 8px 7px;
       border: 0;
       color: $grey-90;
       background: $grey-90-10;
 
 
       &:active {
         @include dark-theme-only {
@@ -159,16 +160,17 @@
 
     .cta-link {
       @include dark-theme-only {
         color: $blue-40;
         fill: $blue-40;
       }
 
       font-size: 15px;
+      font-weight: 600;
       line-height: 24px;
       height: 24px;
       width: auto;
       background-size: auto;
       background-position: right 1.5px;
       padding-right: 9px;
       color: $blue-60;
       fill: $blue-60;
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSImage/DSImage.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSImage/DSImage.jsx
@@ -55,33 +55,36 @@ export class DSImage extends React.PureC
     // Also: force JPEG, quality 60, no upscaling, no EXIF data
     // Uses Thumbor: https://thumbor.readthedocs.io/en/latest/usage.html
     return `https://img-getpocket.cdn.mozilla.net/${width}x${height}/filters:format(jpeg):quality(60):no_upscale():strip_exif()/${encodeURIComponent(
       url
     )}`;
   }
 
   componentDidMount() {
-    this.idleCallbackId = window.requestIdleCallback(
+    this.idleCallbackId = this.props.windowObj.requestIdleCallback(
       this.onIdleCallback.bind(this)
     );
     this.observer = new IntersectionObserver(this.onSeen.bind(this), {
       // Assume an image will be eventually seen if it is within
       // half the average Desktop vertical screen size:
       // http://gs.statcounter.com/screen-resolution-stats/desktop/north-america
       rootMargin: `540px`,
     });
     this.observer.observe(ReactDOM.findDOMNode(this));
   }
 
   componentWillUnmount() {
     // Remove observer on unmount
     if (this.observer) {
       this.observer.unobserve(ReactDOM.findDOMNode(this));
     }
+    if (this.idleCallbackId) {
+      this.props.windowObj.cancelIdleCallback(this.idleCallbackId);
+    }
   }
 
   render() {
     let classNames = `ds-image
       ${this.props.extraClassNames ? ` ${this.props.extraClassNames}` : ``}
       ${this.state && this.state.useTransition ? ` use-transition` : ``}
       ${this.state && this.state.isSeen ? ` loaded` : ``}
     `;
@@ -155,9 +158,10 @@ export class DSImage extends React.PureC
 }
 
 DSImage.defaultProps = {
   source: null, // The current source style from Pocket API (always 450px)
   rawSource: null, // Unadulterated image URL to filter through Thumbor
   extraClassNames: null, // Additional classnames to append to component
   optimize: true, // Measure parent container to request exact sizes
   alt_text: null,
+  windowObj: window, // Added to support unit tests
 };
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
@@ -71,16 +71,17 @@ export class DSLinkMenu extends React.Pu
               referrer: "https://getpocket.com/recommendations",
               title: this.props.title,
               type: this.props.type,
               url: this.props.url,
               guid: this.props.id,
               pocket_id: this.props.pocket_id,
               shim: this.props.shim,
               bookmarkGuid: this.props.bookmarkGuid,
+              campaign_id: this.props.campaignId,
             }}
           />
         </ContextMenuButton>
       </div>
     );
   }
 }
 
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
@@ -1,20 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import React from "react";
+import { actionCreators as ac } from "common/Actions.jsm";
 import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
 import { ModalOverlayWrapper } from "content-src/asrouter/components/ModalOverlay/ModalOverlay";
 
 export class DSPrivacyModal extends React.PureComponent {
   constructor(props) {
     super(props);
     this.closeModal = this.closeModal.bind(this);
+    this.onLinkClick = this.onLinkClick.bind(this);
+  }
+
+  onLinkClick(event) {
+    this.props.dispatch(
+      ac.UserEvent({
+        event: "CLICK_PRIVACY_INFO",
+        source: "DS_PRIVACY_MODAL",
+      })
+    );
   }
 
   closeModal() {
     this.props.dispatch({
       type: `HIDE_PRIVACY_INFO`,
       data: {},
     });
   }
@@ -23,17 +34,20 @@ export class DSPrivacyModal extends Reac
     return (
       <ModalOverlayWrapper
         onClose={this.closeModal}
         innerClassName="ds-privacy-modal"
       >
         <div className="privacy-notice">
           <h3 data-l10n-id="newtab-privacy-modal-header" />
           <p data-l10n-id="newtab-privacy-modal-paragraph" />
-          <SafeAnchor url="https://www.mozilla.org/en-US/privacy/firefox/">
+          <SafeAnchor
+            onLinkClick={this.onLinkClick}
+            url="https://www.mozilla.org/en-US/privacy/firefox/"
+          >
             <span data-l10n-id="newtab-privacy-modal-link" />
           </SafeAnchor>
         </div>
         <section className="actions">
           <button
             className="done"
             type="submit"
             onClick={this.closeModal}
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
@@ -137,16 +137,17 @@ export class Hero extends React.PureComp
             dispatch={this.props.dispatch}
             url={heroRec.url}
             title={heroRec.title}
             source={heroRec.domain}
             type={this.props.type}
             pocket_id={heroRec.pocket_id}
             shim={heroRec.shim}
             bookmarkGuid={heroRec.bookmarkGuid}
+            campaignId={heroRec.campaign_id}
           />
         </div>
       );
     }
 
     let list = (
       <List
         recStartingPoint={1}
--- a/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
+++ b/browser/components/newtab/content-src/components/DiscoveryStreamComponents/List/List.jsx
@@ -111,16 +111,17 @@ export class ListItem extends React.Pure
             dispatch={this.props.dispatch}
             url={this.props.url}
             title={this.props.title}
             source={this.props.source}
             type={this.props.type}
             pocket_id={this.props.pocket_id}
             shim={this.props.shim}
             bookmarkGuid={this.props.bookmarkGuid}
+            campaignId={this.props.campaignId}
           />
         )}
       </li>
     );
   }
 }
 
 export const PlaceholderListItem = props => <ListItem placeholder={true} />;
--- a/browser/components/newtab/content-src/lib/link-menu-options.js
+++ b/browser/components/newtab/content-src/lib/link-menu-options.js
@@ -23,16 +23,17 @@ export const LinkMenuOptions = {
   Separator: () => ({ type: "separator" }),
   EmptyItem: () => ({ type: "empty" }),
   ShowPrivacyInfo: site => ({
     id: "newtab-menu-show-privacy-info",
     icon: "info",
     action: {
       type: at.SHOW_PRIVACY_INFO,
     },
+    userEvent: "SHOW_PRIVACY_INFO",
   }),
   RemoveBookmark: site => ({
     id: "newtab-menu-remove-bookmark",
     icon: "bookmark-added",
     action: ac.AlsoToMain({
       type: at.DELETE_BOOKMARK_BY_ID,
       data: site.bookmarkGuid,
     }),
@@ -55,22 +56,30 @@ export const LinkMenuOptions = {
       data: {
         referrer: site.referrer,
         typedBonus: site.typedBonus,
         url: site.url,
       },
     }),
     userEvent: "OPEN_NEW_WINDOW",
   }),
+  // This blocks the url for regular stories,
+  // but also sends a message to DiscoveryStream with campaign_id.
+  // If DiscoveryStream sees this message for a campaign_id
+  // it also blocks it on the campaign_id.
   BlockUrl: (site, index, eventSource) => ({
     id: "newtab-menu-dismiss",
     icon: "dismiss",
     action: ac.AlsoToMain({
       type: at.BLOCK_URL,
-      data: { url: site.open_url || site.url, pocket_id: site.pocket_id },
+      data: {
+        url: site.open_url || site.url,
+        pocket_id: site.pocket_id,
+        ...(site.campaign_id ? { campaign_id: site.campaign_id } : {}),
+      },
     }),
     impression: ac.ImpressionStats({
       source: eventSource,
       block: 0,
       tiles: [
         {
           id: site.guid,
           pos: index,
--- a/browser/components/newtab/content-src/styles/_activity-stream.scss
+++ b/browser/components/newtab/content-src/styles/_activity-stream.scss
@@ -171,9 +171,11 @@ input {
 @import '../asrouter/components/SnippetBase/SnippetBase';
 @import '../asrouter/components/ModalOverlay/ModalOverlay';
 @import '../asrouter/templates/ReturnToAMO/ReturnToAMO';
 @import '../asrouter/templates/SimpleBelowSearchSnippet/SimpleBelowSearchSnippet';
 @import '../asrouter/templates/SimpleSnippet/SimpleSnippet';
 @import '../asrouter/templates/SubmitFormSnippet/SubmitFormSnippet';
 @import '../asrouter/templates/OnboardingMessage/OnboardingMessage';
 @import '../asrouter/templates/EOYSnippet/EOYSnippet';
+@import '../asrouter/components/FxASignupForm/FxASignupForm';
 @import '../asrouter/templates/Trailhead/Trailhead';
+@import '../asrouter/templates/FullPageInterrupt/FullPageInterrupt';
--- a/browser/components/newtab/content-src/styles/_variables.scss
+++ b/browser/components/newtab/content-src/styles/_variables.scss
@@ -65,32 +65,35 @@
 $black-30: rgba($black, 0.3);
 
 // Other colors
 $white: #FFF;
 $white-10: rgba($white, 0.1);
 $white-50: rgba($white, 0.5);
 $white-60: rgba($white, 0.6);
 $white-70: rgba($white, 0.7);
+$ghost-white: #FAFAFC;
 $pocket-teal: #50BCB6;
 $pocket-red: #EF4056;
 $shadow-10: rgba(12, 12, 13, 0.1);
 $bookmark-icon-fill: #0A84FF;
 $download-icon-fill: #12BC00;
 $pocket-icon-fill: #D70022;
 $email-input-focus: rgba($blue-50, 0.3);
 $email-input-invalid: rgba($red-60, 0.3);
 $aw-extra-blue-1: #004EC2;
 $aw-extra-blue-2: #0080FF;
 $aw-extra-blue-3: #00C7FF;
 $about-welcome-gradient: linear-gradient(to bottom, $blue-70 40%, $aw-extra-blue-1 60%, $blue-60 80%, $aw-extra-blue-2 90%, $aw-extra-blue-3 100%);
 $about-welcome-extra-links: #676F7E;
 $firefox-wordmark-default-color: #363959;
 $firefox-wordmark-darktheme-color: $white;
+$trailhead-violet: #7542E5;
 $trailhead-purple: #2B2156;
+$trailhead-purple-80: #36296D;
 $trailhead-blue-60: #0250BB;
 $trailhead-blue-70: #054096;
 
 // Photon transitions from http://design.firefox.com/photon/motion/duration-and-easing.html
 $photon-easing: cubic-bezier(0.07, 0.95, 0, 1);
 
 $border-radius: 3px;
 
--- a/browser/components/newtab/css/activity-stream-linux.css
+++ b/browser/components/newtab/css/activity-stream-linux.css
@@ -2720,16 +2720,17 @@ main {
         color: #B1B1B3; }
     .ds-card .meta .cta-button {
       width: 100%;
       margin: 12px 0 4px;
       box-shadow: none;
       border-radius: 4px;
       height: 32px;
       font-size: 14px;
+      font-weight: 600;
       padding: 5px 8px 7px;
       border: 0;
       color: #0C0C0D;
       background: rgba(12, 12, 13, 0.1); }
       [lwt-newtab-brighttext] .ds-card .meta .cta-button {
         color: #F9F9FA;
         background: rgba(12, 12, 13, 0.3); }
       .ds-card .meta .cta-button:active {
@@ -2742,16 +2743,17 @@ main {
           background: rgba(12, 12, 13, 0.5); }
       .ds-card .meta .cta-button:focus {
         box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
         [lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
           background: rgba(12, 12, 13, 0.3);
           box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
     .ds-card .meta .cta-link {
       font-size: 15px;
+      font-weight: 600;
       line-height: 24px;
       height: 24px;
       width: auto;
       background-size: auto;
       background-position: right 1.5px;
       padding-right: 9px;
       color: #0060DF;
       fill: #0060DF; }
@@ -3931,16 +3933,94 @@ body[lwt-newtab-brighttext] .scene2Icon 
   .EOYSnippetForm .monthly-checkbox-container {
     display: flex;
     width: 100%; }
   .EOYSnippetForm .donation-form-url {
     margin-inline-start: 18px;
     align-self: flex-end;
     display: flex; }
 
+.fxaSignupForm {
+  min-width: 260px;
+  text-align: center; }
+  .fxaSignupForm a {
+    color: #FFF;
+    text-decoration: underline; }
+  .fxaSignupForm input,
+  .fxaSignupForm button {
+    border-radius: 4px;
+    padding: 10px; }
+  .fxaSignupForm h3 {
+    font-size: 36px;
+    font-weight: 200;
+    line-height: 46px;
+    margin: 12px 0 4px; }
+  .fxaSignupForm p {
+    font-size: 15px;
+    line-height: 22px;
+    margin: 0 0 20px; }
+  .fxaSignupForm .fxa-terms {
+    margin: 4px 30px 20px; }
+    .fxaSignupForm .fxa-terms a, .fxaSignupForm .fxa-terms {
+      color: rgba(255, 255, 255, 0.7);
+      font-size: 12px;
+      line-height: 20px; }
+  .fxaSignupForm .fxa-signin {
+    font-size: 16px;
+    margin-top: 19px; }
+    .fxaSignupForm .fxa-signin span {
+      margin-inline-end: 5px; }
+    .fxaSignupForm .fxa-signin button {
+      background-color: initial;
+      text-decoration: underline;
+      color: #FFF;
+      display: inline;
+      padding: 0;
+      width: auto; }
+      .fxaSignupForm .fxa-signin button:hover, .fxaSignupForm .fxa-signin button:focus, .fxaSignupForm .fxa-signin button:active {
+        background-color: initial; }
+  .fxaSignupForm form {
+    position: relative; }
+    .fxaSignupForm form .error.active {
+      inset-inline-start: 0;
+      z-index: 0; }
+  .fxaSignupForm button,
+  .fxaSignupForm input {
+    width: 100%; }
+  .fxaSignupForm input {
+    background-color: #FFF;
+    border: 1px solid #737373;
+    box-shadow: none;
+    color: #38383D;
+    font-size: 15px;
+    transition: border-color 150ms, box-shadow 150ms; }
+    .fxaSignupForm input:hover {
+      border-color: #0C0C0D; }
+    .fxaSignupForm input:focus {
+      border-color: #0A84FF;
+      box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
+    .fxaSignupForm input.invalid {
+      border-color: #D70022; }
+    .fxaSignupForm input.invalid:focus {
+      box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
+  .fxaSignupForm button {
+    background-color: #0060DF;
+    border: 0;
+    cursor: pointer;
+    display: block;
+    font-size: 15px;
+    font-weight: 400;
+    padding: 14px; }
+    .fxaSignupForm button:hover, .fxaSignupForm button:focus {
+      background-color: #0250BB; }
+    .fxaSignupForm button:focus {
+      outline: dotted 1px; }
+    .fxaSignupForm button:active {
+      background-color: #054096; }
+
 .trailhead {
   background: url("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
   color: #FFF;
   height: auto; }
   .trailhead a {
     color: #FFF;
     text-decoration: underline; }
   .trailhead input,
@@ -3958,16 +4038,21 @@ body[lwt-newtab-brighttext] .scene2Icon 
     line-height: 46px;
     margin: 0; }
   .trailhead .trailheadContent .trailheadLearn {
     display: block;
     margin-top: 30px; }
     @media (min-width: 850px) {
       .trailhead .trailheadContent .trailheadLearn {
         margin-inline-start: 74px; } }
+  .trailhead .trailhead-join-form {
+    background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
+    color: #FFF;
+    min-width: 260px;
+    padding-top: 100px; }
   .trailhead.syncCohort {
     left: calc(50% - 430px);
     width: 860px; }
     @media (max-width: 860px) {
       .trailhead.syncCohort {
         left: 0;
         width: 100%; } }
     .trailhead.syncCohort .trailheadInner {
@@ -4018,75 +4103,16 @@ body[lwt-newtab-brighttext] .scene2Icon 
       @media (min-width: 850px) {
         .trailhead .trailheadBenefits h2 {
           padding-inline-start: 0; } }
     .trailhead .trailheadBenefits p {
       color: #FFF;
       font-size: 15px;
       line-height: 22px;
       margin: 4px 0 15px; }
-  .trailhead .trailheadForm {
-    background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
-    min-width: 260px;
-    padding-top: 100px;
-    text-align: center; }
-    .trailhead .trailheadForm h3 {
-      font-size: 36px;
-      font-weight: 200;
-      line-height: 46px;
-      margin: 12px 0 4px; }
-    .trailhead .trailheadForm p {
-      color: #FFF;
-      font-size: 15px;
-      line-height: 22px;
-      margin: 0 0 20px; }
-    .trailhead .trailheadForm .trailheadTerms {
-      margin: 4px 30px 20px; }
-      .trailhead .trailheadForm .trailheadTerms a, .trailhead .trailheadForm .trailheadTerms {
-        color: rgba(255, 255, 255, 0.7);
-        font-size: 12px;
-        line-height: 20px; }
-    .trailhead .trailheadForm form {
-      position: relative; }
-      .trailhead .trailheadForm form .error.active {
-        inset-inline-start: 0;
-        z-index: 0; }
-    .trailhead .trailheadForm button,
-    .trailhead .trailheadForm input {
-      width: 100%; }
-    .trailhead .trailheadForm input {
-      background-color: #FFF;
-      border: 1px solid #737373;
-      box-shadow: none;
-      color: #38383D;
-      font-size: 15px;
-      transition: border-color 150ms, box-shadow 150ms; }
-      .trailhead .trailheadForm input:hover {
-        border-color: #0C0C0D; }
-      .trailhead .trailheadForm input:focus {
-        border-color: #0A84FF;
-        box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
-      .trailhead .trailheadForm input.invalid {
-        border-color: #D70022; }
-      .trailhead .trailheadForm input.invalid:focus {
-        box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
-    .trailhead .trailheadForm button {
-      background-color: #0060DF;
-      border: 0;
-      cursor: pointer;
-      display: block;
-      font-size: 15px;
-      font-weight: 400;
-      padding: 14px; }
-      .trailhead .trailheadForm button:hover, .trailhead .trailheadForm button:focus {
-        background-color: #0250BB; }
-      .trailhead .trailheadForm button:focus {
-        outline: dotted 1px; }
-      .trailhead .trailheadForm button:active {
-        background-color: #054096; }
   .trailhead .trailheadStart {
     border: 1px solid rgba(255, 255, 255, 0.5);
     cursor: pointer;
     display: block;
     font-size: 15px;
     font-weight: 400;
     margin: 0 auto 40px;
     min-width: 300px;
@@ -4253,19 +4279,177 @@ body[lwt-newtab-brighttext] .scene2Icon 
 @keyframes fade-down {
   0% {
     opacity: 0;
     transform: translateY(-15px); }
   100% {
     opacity: 1;
     transform: translateY(0); } }
 
-.firstrun-title {
-  background: url("chrome://branding/content/about-logo.png") top left no-repeat;
-  background-size: 90px 90px;
-  margin: 40px 0 10px;
-  padding-top: 110px; }
-  @media screen and (max-width: 790px) {
-    .firstrun-title {
-      background: url("chrome://branding/content/about-logo.png") top center no-repeat;
-      background-size: 90px 90px; } }
-  .firstrun-title:dir(rtl) {
-    background-position: top right; }
+.activity-stream.welcome {
+  overflow: hidden; }
+
+.activity-stream:not(.welcome) .fullpage-wrapper {
+  display: none; }
+
+.fullpage-wrapper {
+  align-content: center;
+  display: flex;
+  flex-direction: column;
+  overflow-x: auto;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 21000;
+  background-color: #FAFAFC; }
+  .fullpage-wrapper + div {
+    opacity: 0; }
+  .fullpage-wrapper .fullpage-icon {
+    background-position-x: left;
+    background-repeat: no-repeat;
+    background-size: contain; }
+    .fullpage-wrapper .fullpage-icon:dir(rtl) {
+      background-position-x: right; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-icon {
+        background-position: center; } }
+  .fullpage-wrapper .brand-logo {
+    background-image: url("chrome://branding/content/about-logo.png");
+    margin: 20px 10px 10px 20px;
+    padding-bottom: 50px; }
+  .fullpage-wrapper .welcome-title,
+  .fullpage-wrapper .welcome-subtitle {
+    align-self: center;
+    margin: 0; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .welcome-title,
+      .fullpage-wrapper .welcome-subtitle {
+        text-align: center; } }
+  .fullpage-wrapper .welcome-title {
+    color: #36296D;
+    font-size: 46px;
+    font-weight: 600;
+    line-height: 62px; }
+  .fullpage-wrapper .welcome-subtitle {
+    color: #7542E5;
+    font-size: 20px;
+    line-height: 27px; }
+  .fullpage-wrapper .container {
+    display: flex;
+    align-self: center;
+    padding: 50px 0; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .container {
+        flex-direction: column;
+        width: 352px;
+        text-align: center; } }
+  .fullpage-wrapper .fullpage-left-section {
+    position: relative;
+    width: 538px;
+    font-size: 18px;
+    line-height: 30px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-left-section {
+        width: 352px; } }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-content {
+      color: #4A4A4F;
+      display: inline;
+      margin: 0;
+      margin-inline-end: 2px; }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-link {
+      color: #0060DF;
+      display: block;
+      text-decoration: underline;
+      margin-bottom: 30px; }
+      .fullpage-wrapper .fullpage-left-section .fullpage-left-link:hover, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:active, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:focus {
+        color: #0060DF; }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-title {
+      margin: 0;
+      color: #36296D;
+      font-size: 36px;
+      line-height: 48px; }
+    .fullpage-wrapper .fullpage-left-section .fx-systems-icons {
+      height: 33px;
+      display: block;
+      background-image: url("../data/content/assets/trailhead/firefox-systems.png");
+      margin-bottom: 20px; }
+  .fullpage-wrapper .fullpage-form {
+    position: relative;
+    text-align: center;
+    margin-inline-start: 36px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-form {
+        margin-inline-start: 0; } }
+    .fullpage-wrapper .fullpage-form .fxaSignupForm {
+      width: 356px;
+      padding: 25px;
+      box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15);
+      border-radius: 6px;
+      background: #FFF; }
+    .fullpage-wrapper .fullpage-form .fxa-terms {
+      margin: 4px 0 20px; }
+      .fullpage-wrapper .fullpage-form .fxa-terms a, .fullpage-wrapper .fullpage-form .fxa-terms {
+        color: #4A4A4F;
+        font-size: 12px;
+        line-height: 16px; }
+    .fullpage-wrapper .fullpage-form .fxa-signin {
+      color: #4A4A4F;
+      line-height: 30px;
+      opacity: 0.77; }
+      .fullpage-wrapper .fullpage-form .fxa-signin button {
+        color: #0060DF; }
+    .fullpage-wrapper .fullpage-form h3 {
+      color: #36296D;
+      font-weight: 400;
+      font-size: 36px;
+      line-height: 36px;
+      margin: 0;
+      padding: 8px; }
+    .fullpage-wrapper .fullpage-form h3 + p {
+      color: #4A4A4F;
+      font-size: 16px;
+      line-height: 20px;
+      opacity: 0.77; }
+    .fullpage-wrapper .fullpage-form input {
+      background: #FFF;
+      border: 1px solid #D7D7DB;
+      border-radius: 2px; }
+      .fullpage-wrapper .fullpage-form input:hover {
+        border-color: #737373; }
+      .fullpage-wrapper .fullpage-form input.invalid {
+        border-color: #D70022; }
+    .fullpage-wrapper .fullpage-form button {
+      color: #FFF;
+      font-size: 16px; }
+      .fullpage-wrapper .fullpage-form button:focus {
+        outline: dotted 1px #737373; }
+  .fullpage-wrapper .section-divider::after {
+    content: '';
+    display: block;
+    border-bottom: 0.5px solid #D7D7DB; }
+  .fullpage-wrapper .trailheadCard {
+    box-shadow: none;
+    background: none;
+    text-align: center;
+    width: 320px;
+    padding: 18px; }
+    .fullpage-wrapper .trailheadCard .onboardingTitle {
+      color: #0C0C0D; }
+    .fullpage-wrapper .trailheadCard .onboardingText {
+      font-weight: normal;
+      color: #4A4A4F;
+      margin-top: 4px; }
+    .fullpage-wrapper .trailheadCard .onboardingButton {
+      color: #4A4A4F;
+      background: rgba(12, 12, 13, 0.1); }
+      .fullpage-wrapper .trailheadCard .onboardingButton:focus, .fullpage-wrapper .trailheadCard .onboardingButton:hover {
+        background: rgba(12, 12, 13, 0.2); }
+      .fullpage-wrapper .trailheadCard .onboardingButton:active {
+        background: rgba(12, 12, 13, 0.3); }
+    .fullpage-wrapper .trailheadCard .onboardingMessageImage {
+      height: 112px;
+      width: 154px;
+      background-size: 154px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .trailheadCard {
+        width: 352px; } }
--- a/browser/components/newtab/css/activity-stream-mac.css
+++ b/browser/components/newtab/css/activity-stream-mac.css
@@ -2723,16 +2723,17 @@ main {
         color: #B1B1B3; }
     .ds-card .meta .cta-button {
       width: 100%;
       margin: 12px 0 4px;
       box-shadow: none;
       border-radius: 4px;
       height: 32px;
       font-size: 14px;
+      font-weight: 600;
       padding: 5px 8px 7px;
       border: 0;
       color: #0C0C0D;
       background: rgba(12, 12, 13, 0.1); }
       [lwt-newtab-brighttext] .ds-card .meta .cta-button {
         color: #F9F9FA;
         background: rgba(12, 12, 13, 0.3); }
       .ds-card .meta .cta-button:active {
@@ -2745,16 +2746,17 @@ main {
           background: rgba(12, 12, 13, 0.5); }
       .ds-card .meta .cta-button:focus {
         box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
         [lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
           background: rgba(12, 12, 13, 0.3);
           box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
     .ds-card .meta .cta-link {
       font-size: 15px;
+      font-weight: 600;
       line-height: 24px;
       height: 24px;
       width: auto;
       background-size: auto;
       background-position: right 1.5px;
       padding-right: 9px;
       color: #0060DF;
       fill: #0060DF; }
@@ -3934,16 +3936,94 @@ body[lwt-newtab-brighttext] .scene2Icon 
   .EOYSnippetForm .monthly-checkbox-container {
     display: flex;
     width: 100%; }
   .EOYSnippetForm .donation-form-url {
     margin-inline-start: 18px;
     align-self: flex-end;
     display: flex; }
 
+.fxaSignupForm {
+  min-width: 260px;
+  text-align: center; }
+  .fxaSignupForm a {
+    color: #FFF;
+    text-decoration: underline; }
+  .fxaSignupForm input,
+  .fxaSignupForm button {
+    border-radius: 4px;
+    padding: 10px; }
+  .fxaSignupForm h3 {
+    font-size: 36px;
+    font-weight: 200;
+    line-height: 46px;
+    margin: 12px 0 4px; }
+  .fxaSignupForm p {
+    font-size: 15px;
+    line-height: 22px;
+    margin: 0 0 20px; }
+  .fxaSignupForm .fxa-terms {
+    margin: 4px 30px 20px; }
+    .fxaSignupForm .fxa-terms a, .fxaSignupForm .fxa-terms {
+      color: rgba(255, 255, 255, 0.7);
+      font-size: 12px;
+      line-height: 20px; }
+  .fxaSignupForm .fxa-signin {
+    font-size: 16px;
+    margin-top: 19px; }
+    .fxaSignupForm .fxa-signin span {
+      margin-inline-end: 5px; }
+    .fxaSignupForm .fxa-signin button {
+      background-color: initial;
+      text-decoration: underline;
+      color: #FFF;
+      display: inline;
+      padding: 0;
+      width: auto; }
+      .fxaSignupForm .fxa-signin button:hover, .fxaSignupForm .fxa-signin button:focus, .fxaSignupForm .fxa-signin button:active {
+        background-color: initial; }
+  .fxaSignupForm form {
+    position: relative; }
+    .fxaSignupForm form .error.active {
+      inset-inline-start: 0;
+      z-index: 0; }
+  .fxaSignupForm button,
+  .fxaSignupForm input {
+    width: 100%; }
+  .fxaSignupForm input {
+    background-color: #FFF;
+    border: 1px solid #737373;
+    box-shadow: none;
+    color: #38383D;
+    font-size: 15px;
+    transition: border-color 150ms, box-shadow 150ms; }
+    .fxaSignupForm input:hover {
+      border-color: #0C0C0D; }
+    .fxaSignupForm input:focus {
+      border-color: #0A84FF;
+      box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
+    .fxaSignupForm input.invalid {
+      border-color: #D70022; }
+    .fxaSignupForm input.invalid:focus {
+      box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
+  .fxaSignupForm button {
+    background-color: #0060DF;
+    border: 0;
+    cursor: pointer;
+    display: block;
+    font-size: 15px;
+    font-weight: 400;
+    padding: 14px; }
+    .fxaSignupForm button:hover, .fxaSignupForm button:focus {
+      background-color: #0250BB; }
+    .fxaSignupForm button:focus {
+      outline: dotted 1px; }
+    .fxaSignupForm button:active {
+      background-color: #054096; }
+
 .trailhead {
   background: url("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
   color: #FFF;
   height: auto; }
   .trailhead a {
     color: #FFF;
     text-decoration: underline; }
   .trailhead input,
@@ -3961,16 +4041,21 @@ body[lwt-newtab-brighttext] .scene2Icon 
     line-height: 46px;
     margin: 0; }
   .trailhead .trailheadContent .trailheadLearn {
     display: block;
     margin-top: 30px; }
     @media (min-width: 850px) {
       .trailhead .trailheadContent .trailheadLearn {
         margin-inline-start: 74px; } }
+  .trailhead .trailhead-join-form {
+    background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
+    color: #FFF;
+    min-width: 260px;
+    padding-top: 100px; }
   .trailhead.syncCohort {
     left: calc(50% - 430px);
     width: 860px; }
     @media (max-width: 860px) {
       .trailhead.syncCohort {
         left: 0;
         width: 100%; } }
     .trailhead.syncCohort .trailheadInner {
@@ -4021,75 +4106,16 @@ body[lwt-newtab-brighttext] .scene2Icon 
       @media (min-width: 850px) {
         .trailhead .trailheadBenefits h2 {
           padding-inline-start: 0; } }
     .trailhead .trailheadBenefits p {
       color: #FFF;
       font-size: 15px;
       line-height: 22px;
       margin: 4px 0 15px; }
-  .trailhead .trailheadForm {
-    background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
-    min-width: 260px;
-    padding-top: 100px;
-    text-align: center; }
-    .trailhead .trailheadForm h3 {
-      font-size: 36px;
-      font-weight: 200;
-      line-height: 46px;
-      margin: 12px 0 4px; }
-    .trailhead .trailheadForm p {
-      color: #FFF;
-      font-size: 15px;
-      line-height: 22px;
-      margin: 0 0 20px; }
-    .trailhead .trailheadForm .trailheadTerms {
-      margin: 4px 30px 20px; }
-      .trailhead .trailheadForm .trailheadTerms a, .trailhead .trailheadForm .trailheadTerms {
-        color: rgba(255, 255, 255, 0.7);
-        font-size: 12px;
-        line-height: 20px; }
-    .trailhead .trailheadForm form {
-      position: relative; }
-      .trailhead .trailheadForm form .error.active {
-        inset-inline-start: 0;
-        z-index: 0; }
-    .trailhead .trailheadForm button,
-    .trailhead .trailheadForm input {
-      width: 100%; }
-    .trailhead .trailheadForm input {
-      background-color: #FFF;
-      border: 1px solid #737373;
-      box-shadow: none;
-      color: #38383D;
-      font-size: 15px;
-      transition: border-color 150ms, box-shadow 150ms; }
-      .trailhead .trailheadForm input:hover {
-        border-color: #0C0C0D; }
-      .trailhead .trailheadForm input:focus {
-        border-color: #0A84FF;
-        box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
-      .trailhead .trailheadForm input.invalid {
-        border-color: #D70022; }
-      .trailhead .trailheadForm input.invalid:focus {
-        box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
-    .trailhead .trailheadForm button {
-      background-color: #0060DF;
-      border: 0;
-      cursor: pointer;
-      display: block;
-      font-size: 15px;
-      font-weight: 400;
-      padding: 14px; }
-      .trailhead .trailheadForm button:hover, .trailhead .trailheadForm button:focus {
-        background-color: #0250BB; }
-      .trailhead .trailheadForm button:focus {
-        outline: dotted 1px; }
-      .trailhead .trailheadForm button:active {
-        background-color: #054096; }
   .trailhead .trailheadStart {
     border: 1px solid rgba(255, 255, 255, 0.5);
     cursor: pointer;
     display: block;
     font-size: 15px;
     font-weight: 400;
     margin: 0 auto 40px;
     min-width: 300px;
@@ -4256,19 +4282,177 @@ body[lwt-newtab-brighttext] .scene2Icon 
 @keyframes fade-down {
   0% {
     opacity: 0;
     transform: translateY(-15px); }
   100% {
     opacity: 1;
     transform: translateY(0); } }
 
-.firstrun-title {
-  background: url("chrome://branding/content/about-logo.png") top left no-repeat;
-  background-size: 90px 90px;
-  margin: 40px 0 10px;
-  padding-top: 110px; }
-  @media screen and (max-width: 790px) {
-    .firstrun-title {
-      background: url("chrome://branding/content/about-logo.png") top center no-repeat;
-      background-size: 90px 90px; } }
-  .firstrun-title:dir(rtl) {
-    background-position: top right; }
+.activity-stream.welcome {
+  overflow: hidden; }
+
+.activity-stream:not(.welcome) .fullpage-wrapper {
+  display: none; }
+
+.fullpage-wrapper {
+  align-content: center;
+  display: flex;
+  flex-direction: column;
+  overflow-x: auto;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 21000;
+  background-color: #FAFAFC; }
+  .fullpage-wrapper + div {
+    opacity: 0; }
+  .fullpage-wrapper .fullpage-icon {
+    background-position-x: left;
+    background-repeat: no-repeat;
+    background-size: contain; }
+    .fullpage-wrapper .fullpage-icon:dir(rtl) {
+      background-position-x: right; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-icon {
+        background-position: center; } }
+  .fullpage-wrapper .brand-logo {
+    background-image: url("chrome://branding/content/about-logo.png");
+    margin: 20px 10px 10px 20px;
+    padding-bottom: 50px; }
+  .fullpage-wrapper .welcome-title,
+  .fullpage-wrapper .welcome-subtitle {
+    align-self: center;
+    margin: 0; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .welcome-title,
+      .fullpage-wrapper .welcome-subtitle {
+        text-align: center; } }
+  .fullpage-wrapper .welcome-title {
+    color: #36296D;
+    font-size: 46px;
+    font-weight: 600;
+    line-height: 62px; }
+  .fullpage-wrapper .welcome-subtitle {
+    color: #7542E5;
+    font-size: 20px;
+    line-height: 27px; }
+  .fullpage-wrapper .container {
+    display: flex;
+    align-self: center;
+    padding: 50px 0; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .container {
+        flex-direction: column;
+        width: 352px;
+        text-align: center; } }
+  .fullpage-wrapper .fullpage-left-section {
+    position: relative;
+    width: 538px;
+    font-size: 18px;
+    line-height: 30px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-left-section {
+        width: 352px; } }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-content {
+      color: #4A4A4F;
+      display: inline;
+      margin: 0;
+      margin-inline-end: 2px; }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-link {
+      color: #0060DF;
+      display: block;
+      text-decoration: underline;
+      margin-bottom: 30px; }
+      .fullpage-wrapper .fullpage-left-section .fullpage-left-link:hover, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:active, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:focus {
+        color: #0060DF; }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-title {
+      margin: 0;
+      color: #36296D;
+      font-size: 36px;
+      line-height: 48px; }
+    .fullpage-wrapper .fullpage-left-section .fx-systems-icons {
+      height: 33px;
+      display: block;
+      background-image: url("../data/content/assets/trailhead/firefox-systems.png");
+      margin-bottom: 20px; }
+  .fullpage-wrapper .fullpage-form {
+    position: relative;
+    text-align: center;
+    margin-inline-start: 36px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-form {
+        margin-inline-start: 0; } }
+    .fullpage-wrapper .fullpage-form .fxaSignupForm {
+      width: 356px;
+      padding: 25px;
+      box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15);
+      border-radius: 6px;
+      background: #FFF; }
+    .fullpage-wrapper .fullpage-form .fxa-terms {
+      margin: 4px 0 20px; }
+      .fullpage-wrapper .fullpage-form .fxa-terms a, .fullpage-wrapper .fullpage-form .fxa-terms {
+        color: #4A4A4F;
+        font-size: 12px;
+        line-height: 16px; }
+    .fullpage-wrapper .fullpage-form .fxa-signin {
+      color: #4A4A4F;
+      line-height: 30px;
+      opacity: 0.77; }
+      .fullpage-wrapper .fullpage-form .fxa-signin button {
+        color: #0060DF; }
+    .fullpage-wrapper .fullpage-form h3 {
+      color: #36296D;
+      font-weight: 400;
+      font-size: 36px;
+      line-height: 36px;
+      margin: 0;
+      padding: 8px; }
+    .fullpage-wrapper .fullpage-form h3 + p {
+      color: #4A4A4F;
+      font-size: 16px;
+      line-height: 20px;
+      opacity: 0.77; }
+    .fullpage-wrapper .fullpage-form input {
+      background: #FFF;
+      border: 1px solid #D7D7DB;
+      border-radius: 2px; }
+      .fullpage-wrapper .fullpage-form input:hover {
+        border-color: #737373; }
+      .fullpage-wrapper .fullpage-form input.invalid {
+        border-color: #D70022; }
+    .fullpage-wrapper .fullpage-form button {
+      color: #FFF;
+      font-size: 16px; }
+      .fullpage-wrapper .fullpage-form button:focus {
+        outline: dotted 1px #737373; }
+  .fullpage-wrapper .section-divider::after {
+    content: '';
+    display: block;
+    border-bottom: 0.5px solid #D7D7DB; }
+  .fullpage-wrapper .trailheadCard {
+    box-shadow: none;
+    background: none;
+    text-align: center;
+    width: 320px;
+    padding: 18px; }
+    .fullpage-wrapper .trailheadCard .onboardingTitle {
+      color: #0C0C0D; }
+    .fullpage-wrapper .trailheadCard .onboardingText {
+      font-weight: normal;
+      color: #4A4A4F;
+      margin-top: 4px; }
+    .fullpage-wrapper .trailheadCard .onboardingButton {
+      color: #4A4A4F;
+      background: rgba(12, 12, 13, 0.1); }
+      .fullpage-wrapper .trailheadCard .onboardingButton:focus, .fullpage-wrapper .trailheadCard .onboardingButton:hover {
+        background: rgba(12, 12, 13, 0.2); }
+      .fullpage-wrapper .trailheadCard .onboardingButton:active {
+        background: rgba(12, 12, 13, 0.3); }
+    .fullpage-wrapper .trailheadCard .onboardingMessageImage {
+      height: 112px;
+      width: 154px;
+      background-size: 154px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .trailheadCard {
+        width: 352px; } }
--- a/browser/components/newtab/css/activity-stream-windows.css
+++ b/browser/components/newtab/css/activity-stream-windows.css
@@ -2720,16 +2720,17 @@ main {
         color: #B1B1B3; }
     .ds-card .meta .cta-button {
       width: 100%;
       margin: 12px 0 4px;
       box-shadow: none;
       border-radius: 4px;
       height: 32px;
       font-size: 14px;
+      font-weight: 600;
       padding: 5px 8px 7px;
       border: 0;
       color: #0C0C0D;
       background: rgba(12, 12, 13, 0.1); }
       [lwt-newtab-brighttext] .ds-card .meta .cta-button {
         color: #F9F9FA;
         background: rgba(12, 12, 13, 0.3); }
       .ds-card .meta .cta-button:active {
@@ -2742,16 +2743,17 @@ main {
           background: rgba(12, 12, 13, 0.5); }
       .ds-card .meta .cta-button:focus {
         box-shadow: 0 0 0 2px #FFF, 0 0 0 5px rgba(10, 132, 255, 0.5); }
         [lwt-newtab-brighttext] .ds-card .meta .cta-button:focus {
           background: rgba(12, 12, 13, 0.3);
           box-shadow: 0 0 0 2px #2A2A2E, 0 0 0 5px rgba(10, 132, 255, 0.5); }
     .ds-card .meta .cta-link {
       font-size: 15px;
+      font-weight: 600;
       line-height: 24px;
       height: 24px;
       width: auto;
       background-size: auto;
       background-position: right 1.5px;
       padding-right: 9px;
       color: #0060DF;
       fill: #0060DF; }
@@ -3931,16 +3933,94 @@ body[lwt-newtab-brighttext] .scene2Icon 
   .EOYSnippetForm .monthly-checkbox-container {
     display: flex;
     width: 100%; }
   .EOYSnippetForm .donation-form-url {
     margin-inline-start: 18px;
     align-self: flex-end;
     display: flex; }
 
+.fxaSignupForm {
+  min-width: 260px;
+  text-align: center; }
+  .fxaSignupForm a {
+    color: #FFF;
+    text-decoration: underline; }
+  .fxaSignupForm input,
+  .fxaSignupForm button {
+    border-radius: 4px;
+    padding: 10px; }
+  .fxaSignupForm h3 {
+    font-size: 36px;
+    font-weight: 200;
+    line-height: 46px;
+    margin: 12px 0 4px; }
+  .fxaSignupForm p {
+    font-size: 15px;
+    line-height: 22px;
+    margin: 0 0 20px; }
+  .fxaSignupForm .fxa-terms {
+    margin: 4px 30px 20px; }
+    .fxaSignupForm .fxa-terms a, .fxaSignupForm .fxa-terms {
+      color: rgba(255, 255, 255, 0.7);
+      font-size: 12px;
+      line-height: 20px; }
+  .fxaSignupForm .fxa-signin {
+    font-size: 16px;
+    margin-top: 19px; }
+    .fxaSignupForm .fxa-signin span {
+      margin-inline-end: 5px; }
+    .fxaSignupForm .fxa-signin button {
+      background-color: initial;
+      text-decoration: underline;
+      color: #FFF;
+      display: inline;
+      padding: 0;
+      width: auto; }
+      .fxaSignupForm .fxa-signin button:hover, .fxaSignupForm .fxa-signin button:focus, .fxaSignupForm .fxa-signin button:active {
+        background-color: initial; }
+  .fxaSignupForm form {
+    position: relative; }
+    .fxaSignupForm form .error.active {
+      inset-inline-start: 0;
+      z-index: 0; }
+  .fxaSignupForm button,
+  .fxaSignupForm input {
+    width: 100%; }
+  .fxaSignupForm input {
+    background-color: #FFF;
+    border: 1px solid #737373;
+    box-shadow: none;
+    color: #38383D;
+    font-size: 15px;
+    transition: border-color 150ms, box-shadow 150ms; }
+    .fxaSignupForm input:hover {
+      border-color: #0C0C0D; }
+    .fxaSignupForm input:focus {
+      border-color: #0A84FF;
+      box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
+    .fxaSignupForm input.invalid {
+      border-color: #D70022; }
+    .fxaSignupForm input.invalid:focus {
+      box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
+  .fxaSignupForm button {
+    background-color: #0060DF;
+    border: 0;
+    cursor: pointer;
+    display: block;
+    font-size: 15px;
+    font-weight: 400;
+    padding: 14px; }
+    .fxaSignupForm button:hover, .fxaSignupForm button:focus {
+      background-color: #0250BB; }
+    .fxaSignupForm button:focus {
+      outline: dotted 1px; }
+    .fxaSignupForm button:active {
+      background-color: #054096; }
+
 .trailhead {
   background: url("../data/content/assets/trailhead/accounts-form-bg.jpg") bottom/cover;
   color: #FFF;
   height: auto; }
   .trailhead a {
     color: #FFF;
     text-decoration: underline; }
   .trailhead input,
@@ -3958,16 +4038,21 @@ body[lwt-newtab-brighttext] .scene2Icon 
     line-height: 46px;
     margin: 0; }
   .trailhead .trailheadContent .trailheadLearn {
     display: block;
     margin-top: 30px; }
     @media (min-width: 850px) {
       .trailhead .trailheadContent .trailheadLearn {
         margin-inline-start: 74px; } }
+  .trailhead .trailhead-join-form {
+    background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
+    color: #FFF;
+    min-width: 260px;
+    padding-top: 100px; }
   .trailhead.syncCohort {
     left: calc(50% - 430px);
     width: 860px; }
     @media (max-width: 860px) {
       .trailhead.syncCohort {
         left: 0;
         width: 100%; } }
     .trailhead.syncCohort .trailheadInner {
@@ -4018,75 +4103,16 @@ body[lwt-newtab-brighttext] .scene2Icon 
       @media (min-width: 850px) {
         .trailhead .trailheadBenefits h2 {
           padding-inline-start: 0; } }
     .trailhead .trailheadBenefits p {
       color: #FFF;
       font-size: 15px;
       line-height: 22px;
       margin: 4px 0 15px; }
-  .trailhead .trailheadForm {
-    background: url("../data/content/assets/trailhead/firefox-logo.png") top center/100px no-repeat;
-    min-width: 260px;
-    padding-top: 100px;
-    text-align: center; }
-    .trailhead .trailheadForm h3 {
-      font-size: 36px;
-      font-weight: 200;
-      line-height: 46px;
-      margin: 12px 0 4px; }
-    .trailhead .trailheadForm p {
-      color: #FFF;
-      font-size: 15px;
-      line-height: 22px;
-      margin: 0 0 20px; }
-    .trailhead .trailheadForm .trailheadTerms {
-      margin: 4px 30px 20px; }
-      .trailhead .trailheadForm .trailheadTerms a, .trailhead .trailheadForm .trailheadTerms {
-        color: rgba(255, 255, 255, 0.7);
-        font-size: 12px;
-        line-height: 20px; }
-    .trailhead .trailheadForm form {
-      position: relative; }
-      .trailhead .trailheadForm form .error.active {
-        inset-inline-start: 0;
-        z-index: 0; }
-    .trailhead .trailheadForm button,
-    .trailhead .trailheadForm input {
-      width: 100%; }
-    .trailhead .trailheadForm input {
-      background-color: #FFF;
-      border: 1px solid #737373;
-      box-shadow: none;
-      color: #38383D;
-      font-size: 15px;
-      transition: border-color 150ms, box-shadow 150ms; }
-      .trailhead .trailheadForm input:hover {
-        border-color: #0C0C0D; }
-      .trailhead .trailheadForm input:focus {
-        border-color: #0A84FF;
-        box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); }
-      .trailhead .trailheadForm input.invalid {
-        border-color: #D70022; }
-      .trailhead .trailheadForm input.invalid:focus {
-        box-shadow: 0 0 0 3px rgba(215, 0, 34, 0.3); }
-    .trailhead .trailheadForm button {
-      background-color: #0060DF;
-      border: 0;
-      cursor: pointer;
-      display: block;
-      font-size: 15px;
-      font-weight: 400;
-      padding: 14px; }
-      .trailhead .trailheadForm button:hover, .trailhead .trailheadForm button:focus {
-        background-color: #0250BB; }
-      .trailhead .trailheadForm button:focus {
-        outline: dotted 1px; }
-      .trailhead .trailheadForm button:active {
-        background-color: #054096; }
   .trailhead .trailheadStart {
     border: 1px solid rgba(255, 255, 255, 0.5);
     cursor: pointer;
     display: block;
     font-size: 15px;
     font-weight: 400;
     margin: 0 auto 40px;
     min-width: 300px;
@@ -4253,19 +4279,177 @@ body[lwt-newtab-brighttext] .scene2Icon 
 @keyframes fade-down {
   0% {
     opacity: 0;
     transform: translateY(-15px); }
   100% {
     opacity: 1;
     transform: translateY(0); } }
 
-.firstrun-title {
-  background: url("chrome://branding/content/about-logo.png") top left no-repeat;
-  background-size: 90px 90px;
-  margin: 40px 0 10px;
-  padding-top: 110px; }
-  @media screen and (max-width: 790px) {
-    .firstrun-title {
-      background: url("chrome://branding/content/about-logo.png") top center no-repeat;
-      background-size: 90px 90px; } }
-  .firstrun-title:dir(rtl) {
-    background-position: top right; }
+.activity-stream.welcome {
+  overflow: hidden; }
+
+.activity-stream:not(.welcome) .fullpage-wrapper {
+  display: none; }
+
+.fullpage-wrapper {
+  align-content: center;
+  display: flex;
+  flex-direction: column;
+  overflow-x: auto;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 21000;
+  background-color: #FAFAFC; }
+  .fullpage-wrapper + div {
+    opacity: 0; }
+  .fullpage-wrapper .fullpage-icon {
+    background-position-x: left;
+    background-repeat: no-repeat;
+    background-size: contain; }
+    .fullpage-wrapper .fullpage-icon:dir(rtl) {
+      background-position-x: right; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-icon {
+        background-position: center; } }
+  .fullpage-wrapper .brand-logo {
+    background-image: url("chrome://branding/content/about-logo.png");
+    margin: 20px 10px 10px 20px;
+    padding-bottom: 50px; }
+  .fullpage-wrapper .welcome-title,
+  .fullpage-wrapper .welcome-subtitle {
+    align-self: center;
+    margin: 0; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .welcome-title,
+      .fullpage-wrapper .welcome-subtitle {
+        text-align: center; } }
+  .fullpage-wrapper .welcome-title {
+    color: #36296D;
+    font-size: 46px;
+    font-weight: 600;
+    line-height: 62px; }
+  .fullpage-wrapper .welcome-subtitle {
+    color: #7542E5;
+    font-size: 20px;
+    line-height: 27px; }
+  .fullpage-wrapper .container {
+    display: flex;
+    align-self: center;
+    padding: 50px 0; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .container {
+        flex-direction: column;
+        width: 352px;
+        text-align: center; } }
+  .fullpage-wrapper .fullpage-left-section {
+    position: relative;
+    width: 538px;
+    font-size: 18px;
+    line-height: 30px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-left-section {
+        width: 352px; } }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-content {
+      color: #4A4A4F;
+      display: inline;
+      margin: 0;
+      margin-inline-end: 2px; }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-link {
+      color: #0060DF;
+      display: block;
+      text-decoration: underline;
+      margin-bottom: 30px; }
+      .fullpage-wrapper .fullpage-left-section .fullpage-left-link:hover, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:active, .fullpage-wrapper .fullpage-left-section .fullpage-left-link:focus {
+        color: #0060DF; }
+    .fullpage-wrapper .fullpage-left-section .fullpage-left-title {
+      margin: 0;
+      color: #36296D;
+      font-size: 36px;
+      line-height: 48px; }
+    .fullpage-wrapper .fullpage-left-section .fx-systems-icons {
+      height: 33px;
+      display: block;
+      background-image: url("../data/content/assets/trailhead/firefox-systems.png");
+      margin-bottom: 20px; }
+  .fullpage-wrapper .fullpage-form {
+    position: relative;
+    text-align: center;
+    margin-inline-start: 36px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .fullpage-form {
+        margin-inline-start: 0; } }
+    .fullpage-wrapper .fullpage-form .fxaSignupForm {
+      width: 356px;
+      padding: 25px;
+      box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.15);
+      border-radius: 6px;
+      background: #FFF; }
+    .fullpage-wrapper .fullpage-form .fxa-terms {
+      margin: 4px 0 20px; }
+      .fullpage-wrapper .fullpage-form .fxa-terms a, .fullpage-wrapper .fullpage-form .fxa-terms {
+        color: #4A4A4F;
+        font-size: 12px;
+        line-height: 16px; }
+    .fullpage-wrapper .fullpage-form .fxa-signin {
+      color: #4A4A4F;
+      line-height: 30px;
+      opacity: 0.77; }
+      .fullpage-wrapper .fullpage-form .fxa-signin button {
+        color: #0060DF; }
+    .fullpage-wrapper .fullpage-form h3 {
+      color: #36296D;
+      font-weight: 400;
+      font-size: 36px;
+      line-height: 36px;
+      margin: 0;
+      padding: 8px; }
+    .fullpage-wrapper .fullpage-form h3 + p {
+      color: #4A4A4F;
+      font-size: 16px;
+      line-height: 20px;
+      opacity: 0.77; }
+    .fullpage-wrapper .fullpage-form input {
+      background: #FFF;
+      border: 1px solid #D7D7DB;
+      border-radius: 2px; }
+      .fullpage-wrapper .fullpage-form input:hover {
+        border-color: #737373; }
+      .fullpage-wrapper .fullpage-form input.invalid {
+        border-color: #D70022; }
+    .fullpage-wrapper .fullpage-form button {
+      color: #FFF;
+      font-size: 16px; }
+      .fullpage-wrapper .fullpage-form button:focus {
+        outline: dotted 1px #737373; }
+  .fullpage-wrapper .section-divider::after {
+    content: '';
+    display: block;
+    border-bottom: 0.5px solid #D7D7DB; }
+  .fullpage-wrapper .trailheadCard {
+    box-shadow: none;
+    background: none;
+    text-align: center;
+    width: 320px;
+    padding: 18px; }
+    .fullpage-wrapper .trailheadCard .onboardingTitle {
+      color: #0C0C0D; }
+    .fullpage-wrapper .trailheadCard .onboardingText {
+      font-weight: normal;
+      color: #4A4A4F;
+      margin-top: 4px; }
+    .fullpage-wrapper .trailheadCard .onboardingButton {
+      color: #4A4A4F;
+      background: rgba(12, 12, 13, 0.1); }
+      .fullpage-wrapper .trailheadCard .onboardingButton:focus, .fullpage-wrapper .trailheadCard .onboardingButton:hover {
+        background: rgba(12, 12, 13, 0.2); }
+      .fullpage-wrapper .trailheadCard .onboardingButton:active {
+        background: rgba(12, 12, 13, 0.3); }
+    .fullpage-wrapper .trailheadCard .onboardingMessageImage {
+      height: 112px;
+      width: 154px;
+      background-size: 154px; }
+    @media screen and (max-width: 850px) {
+      .fullpage-wrapper .trailheadCard {
+        width: 352px; } }
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -88,25 +88,25 @@
 /******/ ([
 /* 0 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var content_src_components_Base_Base__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
-/* harmony import */ var content_src_lib_detect_user_session_start__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55);
+/* harmony import */ var content_src_lib_detect_user_session_start__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(57);
 /* harmony import */ var content_src_lib_init_store__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(26);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(12);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(60);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(62);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
 
 
@@ -189,25 +189,25 @@ 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_TARGETING_UPDATE", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "CLEAR_PREF", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS", "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_RETRY_FEED", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_PLACEMENTS", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_BLOCKED", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_PRIVACY_INFO", "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", "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_PRIVACY_INFO", "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", "CLEAR_PREF", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS", "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_RETRY_FEED", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_PLACEMENTS", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_BLOCKED", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_PRIVACY_INFO", "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", "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_PRIVACY_INFO", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SUBMIT_SIGNIN", "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", "ENABLE_FIREFOX_MONITOR", "OPEN_PROTECTION_PANEL"]) {
+for (const type of ["HIGHLIGHT_FEATURE", "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", "ENABLE_FIREFOX_MONITOR", "OPEN_PROTECTION_PANEL"]) {
   ASRouterActions[type] = type;
 } // Helper function for creating routed actions between content and main
 // Not intended to be used by consumers
 
 
 function _RouteMessage(action, options) {
   const meta = action.meta ? { ...action.meta
   } : {};
@@ -557,25 +557,25 @@ var actionUtils = {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Base", function() { return _Base; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BaseContent", function() { return BaseContent; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Base", function() { return Base; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var content_src_components_ASRouterAdmin_ASRouterAdmin__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
 /* harmony import */ var _asrouter_asrouter_content__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
-/* harmony import */ var content_src_components_ConfirmDialog_ConfirmDialog__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(28);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(26);
+/* harmony import */ var content_src_components_ConfirmDialog_ConfirmDialog__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(30);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var content_src_components_DiscoveryStreamBase_DiscoveryStreamBase__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(56);
-/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(37);
+/* harmony import */ var content_src_components_DiscoveryStreamBase_DiscoveryStreamBase__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(58);
+/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(39);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_7__);
-/* harmony import */ var content_src_components_Search_Search__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(54);
-/* harmony import */ var content_src_components_Sections_Sections__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(42);
+/* harmony import */ var content_src_components_Search_Search__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(56);
+/* harmony import */ var content_src_components_Sections_Sections__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(44);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -754,22 +754,22 @@ const Base = Object(react_redux__WEBPACK
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ToggleStoryButton", function() { return ToggleStoryButton; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TogglePrefCheckbox", function() { return TogglePrefCheckbox; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DiscoveryStreamAdmin", function() { return DiscoveryStreamAdmin; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterAdminInner", function() { return ASRouterAdminInner; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CollapseToggle", function() { return CollapseToggle; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterAdmin", function() { return ASRouterAdmin; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var _asrouter_asrouter_content__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(26);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_2__);
 /* harmony import */ var _asrouter_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(21);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var _SimpleHashRouter__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(27);
+/* harmony import */ var _SimpleHashRouter__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(29);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -1756,26 +1756,26 @@ const ASRouterAdmin = Object(react_redux
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterUtils", function() { return ASRouterUtils; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterUISurface", function() { return ASRouterUISurface; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var content_src_lib_init_store__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6);
-/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(59);
+/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(61);
 /* harmony import */ var _components_ImpressionsWrapper_ImpressionsWrapper__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
-/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(57);
+/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(59);
 /* harmony import */ var content_src_lib_constants__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_6__);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(12);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_7__);
-/* harmony import */ var _templates_template_manifest__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(58);
-/* harmony import */ var _templates_FirstRun_FirstRun__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(61);
+/* harmony import */ var _templates_template_manifest__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(60);
+/* harmony import */ var _templates_FirstRun_FirstRun__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(63);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -1783,17 +1783,17 @@ function _extends() { _extends = Object.
 
 
 
 
 
 
 const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
 const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
-const TEMPLATES_ABOVE_PAGE = ["trailhead", "return_to_amo_overlay", "extended_triplets"];
+const TEMPLATES_ABOVE_PAGE = ["trailhead", "full_page_interrupt", "return_to_amo_overlay", "extended_triplets"];
 const FIRST_RUN_TEMPLATES = TEMPLATES_ABOVE_PAGE;
 const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
 const ASRouterUtils = {
   addListener(listener) {
     if (global.RPMAddMessageListener) {
       global.RPMAddMessageListener(INCOMING_MESSAGE_NAME, listener);
     }
   },
@@ -2580,20 +2580,20 @@ module.exports = {"title":"EOYSnippet","
 /***/ }),
 /* 14 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "convertLinks", function() { return convertLinks; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RichText", function() { return RichText; });
-/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(57);
+/* harmony import */ var fluent_react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(59);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(59);
+/* harmony import */ var _rich_text_strings__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(61);
 /* harmony import */ var _template_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(15);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
@@ -2708,99 +2708,59 @@ module.exports = {"title":"SendToDeviceS
 /* 20 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Trailhead", function() { return Trailhead; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var _components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(21);
-/* harmony import */ var _FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(22);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* harmony import */ var _components_FxASignupForm_FxASignupForm__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(22);
+/* harmony import */ var _FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(23);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_4__);
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 
 
 
  // From resource://devtools/client/shared/focus.js
 
 const FOCUSABLE_SELECTOR = ["a[href]:not([tabindex='-1'])", "button:not([disabled]):not([tabindex='-1'])", "iframe:not([tabindex='-1'])", "input:not([disabled]):not([tabindex='-1'])", "select:not([disabled]):not([tabindex='-1'])", "textarea:not([disabled]):not([tabindex='-1'])", "[tabindex]:not([tabindex='-1'])"].join(", ");
-class Trailhead extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureComponent {
+class Trailhead extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent {
   constructor(props) {
     super(props);
     this.closeModal = this.closeModal.bind(this);
-    this.onInputChange = this.onInputChange.bind(this);
     this.onStartBlur = this.onStartBlur.bind(this);
-    this.onSubmit = this.onSubmit.bind(this);
-    this.onInputInvalid = this.onInputInvalid.bind(this);
-    this.state = {
-      emailInput: ""
-    };
   }
 
   get dialog() {
     return this.props.document.getElementById("trailheadDialog");
   }
 
   componentDidMount() {
     // We need to remove hide-main since we should show it underneath everything that has rendered
     this.props.document.body.classList.remove("hide-main"); // The rest of the page is "hidden" to screen readers when the modal is open
 
-    this.props.document.getElementById("root").setAttribute("aria-hidden", "true"); // Start with focus in the email input box
-
-    const input = this.dialog.querySelector("input[name=email]");
-
-    if (input) {
-      input.focus();
-    }
-  }
-
-  onInputChange(e) {
-    let error = e.target.previousSibling;
-    this.setState({
-      emailInput: e.target.value
-    });
-    error.classList.remove("active");
-    e.target.classList.remove("invalid");
+    this.props.document.getElementById("root").setAttribute("aria-hidden", "true");
   }
 
   onStartBlur(event) {
     // Make sure focus stays within the dialog when tabbing from the button
     const {
       dialog
     } = this;
 
     if (event.relatedTarget && !(dialog.compareDocumentPosition(event.relatedTarget) & dialog.DOCUMENT_POSITION_CONTAINED_BY)) {
       dialog.querySelector(FOCUSABLE_SELECTOR).focus();
     }
   }
 
-  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) {
     global.removeEventListener("visibilitychange", this.closeModal);
     this.props.document.body.classList.remove("welcome");
     this.props.document.getElementById("root").removeAttribute("aria-hidden");
     this.props.onNextScene(); // 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") {
@@ -2822,153 +2782,68 @@ class Trailhead extends react__WEBPACK_I
     const value = {
       has_flow_params: !!this.props.flowParams.flowId.length
     };
     return {
       value
     };
   }
 
-  onInputInvalid(e) {
-    let error = e.target.previousSibling;
-    error.classList.add("active");
-    e.target.classList.add("invalid");
-    e.preventDefault(); // Override built-in form validation popup
-
-    e.target.focus();
-  }
-
   render() {
     const {
       props
     } = this;
     const {
       UTMTerm
     } = props;
     const {
       content
     } = props.message;
     const innerClassName = ["trailhead", content && content.className].filter(v => v).join(" ");
-    return react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_1__["ModalOverlayWrapper"], {
+    return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_1__["ModalOverlayWrapper"], {
       innerClassName: innerClassName,
       onClose: this.closeModal,
       id: "trailheadDialog",
       headerId: "trailheadHeader",
       hasDismissIcon: true
-    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+    }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
       className: "trailheadInner"
-    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+    }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
       className: "trailheadContent"
-    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h1", {
+    }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h1", {
       "data-l10n-id": content.title.string_id,
       id: "trailheadHeader"
-    }), content.subtitle && react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+    }), content.subtitle && react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
       "data-l10n-id": content.subtitle.string_id
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("ul", {
+    }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("ul", {
       className: "trailheadBenefits"
-    }, content.benefits.map(item => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("li", {
+    }, content.benefits.map(item => react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("li", {
       key: item.id,
       className: item.id
-    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h2", {
+    }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("h2", {
       "data-l10n-id": item.title.string_id
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+    }), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("p", {
       "data-l10n-id": item.text.string_id
-    })))), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
+    })))), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("a", {
       className: "trailheadLearn",
       "data-l10n-id": content.learn.text.string_id,
-      href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__["addUtmParams"])(content.learn.url, UTMTerm),
+      href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_3__["addUtmParams"])(content.learn.url, UTMTerm),
       target: "_blank",
       rel: "noopener noreferrer"
-    })), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
-      role: "group",
-      "aria-labelledby": "joinFormHeader",
-      "aria-describedby": "joinFormBody",
-      className: "trailheadForm"
-    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h3", {
-      id: "joinFormHeader",
-      "data-l10n-id": content.form.title.string_id
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
-      id: "joinFormBody",
-      "data-l10n-id": content.form.text.string_id
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("form", {
-      method: "get",
-      action: this.props.fxaEndpoint,
-      target: "_blank",
-      rel: "noopener noreferrer",
-      onSubmit: this.onSubmit
-    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "service",
-      type: "hidden",
-      value: "sync"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "action",
-      type: "hidden",
-      value: "email"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "context",
-      type: "hidden",
-      value: "fx_desktop_v3"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "entrypoint",
-      type: "hidden",
-      value: "activity-stream-firstrun"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "utm_source",
-      type: "hidden",
-      value: "activity-stream"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "utm_campaign",
-      type: "hidden",
-      value: "firstrun"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "utm_term",
-      type: "hidden",
-      value: UTMTerm
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "device_id",
-      type: "hidden",
-      value: this.props.flowParams.deviceId
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "flow_id",
-      type: "hidden",
-      value: this.props.flowParams.flowId
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "flow_begin_time",
-      type: "hidden",
-      value: this.props.flowParams.flowBeginTime
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      name: "style",
-      type: "hidden",
-      value: "trailhead"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
-      "data-l10n-id": "onboarding-join-form-email-error",
-      className: "error"
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("input", {
-      "data-l10n-id": content.form.email.string_id,
-      name: "email",
-      type: "email",
-      onInvalid: this.onInputInvalid,
-      onChange: this.onInputChange
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
-      className: "trailheadTerms",
-      "data-l10n-id": "onboarding-join-form-legal"
-    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
-      "data-l10n-name": "terms",
-      target: "_blank",
-      rel: "noopener noreferrer",
-      href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__["addUtmParams"])("https://accounts.firefox.com/legal/terms", UTMTerm)
-    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
-      "data-l10n-name": "privacy",
-      target: "_blank",
-      rel: "noopener noreferrer",
-      href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_2__["addUtmParams"])("https://accounts.firefox.com/legal/privacy", UTMTerm)
-    })), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("button", {
-      "data-l10n-id": content.form.button.string_id,
-      type: "submit"
-    })))), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("button", {
+    })), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("div", {
+      className: "trailhead-join-form"
+    }, react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(_components_FxASignupForm_FxASignupForm__WEBPACK_IMPORTED_MODULE_2__["FxASignupForm"], {
+      document: this.props.document,
+      content: content,
+      dispatch: this.props.dispatch,
+      fxaEndpoint: this.props.fxaEndpoint,
+      UTMTerm: UTMTerm,
+      flowParams: this.props.flowParams,
+      onClose: this.closeModal
+    }))), react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("button", {
       className: "trailheadStart",
       "data-l10n-id": content.skipButton.string_id,
       onBlur: this.onStartBlur,
       onClick: this.closeModal
     }));
   }
 
 }
@@ -3080,16 +2955,213 @@ class ModalOverlay extends react__WEBPAC
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
 /* 22 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FxASignupForm", function() { return FxASignupForm; });
+/* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
+/* harmony import */ var _templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(23);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+
+class FxASignupForm extends react__WEBPACK_IMPORTED_MODULE_2___default.a.PureComponent {
+  constructor(props) {
+    super(props);
+    this.onSubmit = this.onSubmit.bind(this);
+    this.onInputChange = this.onInputChange.bind(this);
+    this.onInputInvalid = this.onInputInvalid.bind(this);
+    this.handleSignIn = this.handleSignIn.bind(this);
+    this.state = {
+      emailInput: ""
+    };
+  }
+
+  get email() {
+    return this.props.document.getElementById("fxaSignupForm").querySelector("input[name=email]");
+  }
+
+  onSubmit(event) {
+    let userEvent = "SUBMIT_EMAIL";
+    const {
+      email
+    } = event.target.elements;
+
+    if (email.disabled) {
+      userEvent = "SUBMIT_SIGNIN";
+    } else if (!email.value.length) {
+      email.required = true;
+      email.checkValidity();
+      event.preventDefault();
+      return;
+    } // Report to telemetry additional information about the form submission.
+
+
+    const value = {
+      has_flow_params: !!this.props.flowParams.flowId.length
+    };
+    this.props.dispatch(common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].UserEvent({
+      event: userEvent,
+      value
+    }));
+    global.addEventListener("visibilitychange", this.props.onClose);
+  }
+
+  handleSignIn(event) {
+    // Set disabled to prevent email from appearing in url resulting in the wrong page
+    this.email.disabled = true;
+  }
+
+  componentDidMount() {
+    // Start with focus in the email input box
+    if (this.email) {
+      this.email.focus();
+    }
+  }
+
+  onInputChange(e) {
+    let error = e.target.previousSibling;
+    this.setState({
+      emailInput: e.target.value
+    });
+    error.classList.remove("active");
+    e.target.classList.remove("invalid");
+  }
+
+  onInputInvalid(e) {
+    let error = e.target.previousSibling;
+    error.classList.add("active");
+    e.target.classList.add("invalid");
+    e.preventDefault(); // Override built-in form validation popup
+
+    e.target.focus();
+  }
+
+  render() {
+    const {
+      content,
+      UTMTerm
+    } = this.props;
+    return react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
+      id: "fxaSignupForm",
+      role: "group",
+      "aria-labelledby": "joinFormHeader",
+      "aria-describedby": "joinFormBody",
+      className: "fxaSignupForm"
+    }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("h3", {
+      id: "joinFormHeader",
+      "data-l10n-id": content.form.title.string_id
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("p", {
+      id: "joinFormBody",
+      "data-l10n-id": content.form.text.string_id
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("form", {
+      method: "get",
+      action: this.props.fxaEndpoint,
+      target: "_blank",
+      rel: "noopener noreferrer",
+      onSubmit: this.onSubmit
+    }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "service",
+      type: "hidden",
+      value: "sync"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "action",
+      type: "hidden",
+      value: "email"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "context",
+      type: "hidden",
+      value: "fx_desktop_v3"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "entrypoint",
+      type: "hidden",
+      value: "activity-stream-firstrun"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "utm_source",
+      type: "hidden",
+      value: "activity-stream"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "utm_campaign",
+      type: "hidden",
+      value: "firstrun"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "utm_term",
+      type: "hidden",
+      value: UTMTerm
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "device_id",
+      type: "hidden",
+      value: this.props.flowParams.deviceId
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "flow_id",
+      type: "hidden",
+      value: this.props.flowParams.flowId
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "flow_begin_time",
+      type: "hidden",
+      value: this.props.flowParams.flowBeginTime
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      name: "style",
+      type: "hidden",
+      value: "trailhead"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("p", {
+      "data-l10n-id": "onboarding-join-form-email-error",
+      className: "error"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("input", {
+      "data-l10n-id": content.form.email.string_id,
+      name: "email",
+      type: "email",
+      onInvalid: this.onInputInvalid,
+      onChange: this.onInputChange
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("p", {
+      className: "fxa-terms",
+      "data-l10n-id": "onboarding-join-form-legal"
+    }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("a", {
+      "data-l10n-name": "terms",
+      target: "_blank",
+      rel: "noopener noreferrer",
+      href: Object(_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__["addUtmParams"])("https://accounts.firefox.com/legal/terms", UTMTerm)
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("a", {
+      "data-l10n-name": "privacy",
+      target: "_blank",
+      rel: "noopener noreferrer",
+      href: Object(_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_1__["addUtmParams"])("https://accounts.firefox.com/legal/privacy", UTMTerm)
+    })), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("button", {
+      "data-l10n-id": content.form.button.string_id,
+      type: "submit"
+    }), this.props.showSignInLink && react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("div", {
+      className: "fxa-signin"
+    }, react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("span", {
+      "data-l10n-id": "onboarding-join-form-signin-label"
+    }), react__WEBPACK_IMPORTED_MODULE_2___default.a.createElement("button", {
+      "data-l10n-id": "onboarding-join-form-signin",
+      onClick: this.handleSignIn
+    }))));
+  }
+
+}
+FxASignupForm.defaultProps = {
+  document: global.document
+};
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
+
+/***/ }),
+/* 23 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BASE_PARAMS", function() { return BASE_PARAMS; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addUtmParams", function() { return addUtmParams; });
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 const BASE_PARAMS = {
   utm_source: "activity-stream",
   utm_campaign: "firstrun",
@@ -3112,17 +3184,17 @@ function addUtmParams(url, utmTerm) {
   Object.keys(BASE_PARAMS).forEach(key => {
     returnUrl.searchParams.append(key, BASE_PARAMS[key]);
   });
   returnUrl.searchParams.append("utm_term", utmTerm);
   return returnUrl;
 }
 
 /***/ }),
-/* 23 */
+/* 24 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReturnToAMO", function() { return ReturnToAMO; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _components_RichText_RichText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14);
@@ -3212,26 +3284,274 @@ class ReturnToAMO extends react__WEBPACK
       className: "default grey ReturnToAMOGetStarted"
     }, " ", content.secondary_button.label, " ")));
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 24 */
+/* 25 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FxAccounts", function() { return FxAccounts; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FxCards", function() { return FxCards; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FullPageInterrupt", function() { return FullPageInterrupt; });
+/* harmony import */ var _FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(23);
+/* harmony import */ var _components_FxASignupForm_FxASignupForm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(22);
+/* harmony import */ var _templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(26);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
+function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+
+
+const FxAccounts = ({
+  document,
+  content,
+  dispatch,
+  fxaEndpoint,
+  flowParams,
+  removeOverlay,
+  url,
+  UTMTerm
+}) => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_3___default.a.Fragment, null, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+  className: "fullpage-left-section",
+  "aria-labelledby": "fullpage-left-title",
+  "aria-describedby": "fullpage-left-content"
+}, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h1", {
+  id: "fullpage-left-title",
+  className: "fullpage-left-title",
+  "data-l10n-id": "onboarding-welcome-body"
+}), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+  id: "fullpage-left-content",
+  className: "fullpage-left-content",
+  "data-l10n-id": "onboarding-benefit-products-text"
+}), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("p", {
+  className: "fullpage-left-content",
+  "data-l10n-id": "onboarding-benefit-privacy-text"
+}), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("a", {
+  className: "fullpage-left-link",
+  href: Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_0__["addUtmParams"])(url, UTMTerm),
+  target: "_blank",
+  rel: "noopener noreferrer",
+  "data-l10n-id": "onboarding-welcome-learn-more"
+}), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+  className: "fullpage-icon fx-systems-icons"
+})), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+  className: "fullpage-form"
+}, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(_components_FxASignupForm_FxASignupForm__WEBPACK_IMPORTED_MODULE_1__["FxASignupForm"], {
+  document: document,
+  content: content,
+  dispatch: dispatch,
+  fxaEndpoint: fxaEndpoint,
+  UTMTerm: UTMTerm,
+  flowParams: flowParams,
+  onClose: removeOverlay,
+  showSignInLink: true
+})));
+const FxCards = ({
+  cards,
+  onCardAction,
+  sendUserActionTelemetry
+}) => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_3___default.a.Fragment, null, cards.map(card => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__["OnboardingCard"], _extends({
+  key: card.id,
+  className: "trailheadCard",
+  sendUserActionTelemetry: sendUserActionTelemetry,
+  onAction: onCardAction,
+  UISurface: "TRAILHEAD"
+}, card))));
+class FullPageInterrupt extends react__WEBPACK_IMPORTED_MODULE_3___default.a.PureComponent {
+  constructor(props) {
+    super(props);
+    this.removeOverlay = this.removeOverlay.bind(this);
+    this.onCardAction = this.onCardAction.bind(this);
+  }
+
+  componentWillMount() {
+    global.document.body.classList.add("trailhead-fullpage");
+  }
+
+  componentDidMount() {
+    // Hide the page content from screen readers while the full page interrupt is open
+    this.props.document.getElementById("root").setAttribute("aria-hidden", "true");
+  }
+
+  removeOverlay() {
+    window.removeEventListener("visibilitychange", this.removeOverlay);
+    document.body.classList.remove("hide-main", "trailhead-fullpage"); // Re-enable the document for screen readers
+
+    this.props.document.getElementById("root").setAttribute("aria-hidden", "false");
+    this.props.onBlock();
+    document.body.classList.remove("welcome");
+  }
+
+  onCardAction(action) {
+    let actionUpdates = {};
+    const {
+      flowParams,
+      UTMTerm
+    } = this.props;
+
+    if (action.type === "OPEN_URL") {
+      let url = new URL(action.data.args);
+      Object(_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_0__["addUtmParams"])(url, UTMTerm);
+
+      if (action.addFlowParams) {
+        url.searchParams.append("device_id", flowParams.deviceId);
+        url.searchParams.append("flow_id", flowParams.flowId);
+        url.searchParams.append("flow_begin_time", flowParams.flowBeginTime);
+      }
+
+      actionUpdates = {
+        data: { ...action.data,
+          args: url.toString()
+        }
+      };
+    }
+
+    this.props.onAction({ ...action,
+      ...actionUpdates
+    });
+    this.removeOverlay();
+  }
+
+  render() {
+    const {
+      props
+    } = this;
+    const {
+      content
+    } = props.message;
+    const cards = react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(FxCards, {
+      cards: props.cards,
+      onCardAction: this.onCardAction,
+      sendUserActionTelemetry: props.sendUserActionTelemetry
+    });
+    const accounts = react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(FxAccounts, {
+      document: props.document,
+      content: content,
+      dispatch: props.dispatch,
+      fxaEndpoint: props.fxaEndpoint,
+      flowParams: props.flowParams,
+      removeOverlay: this.removeOverlay,
+      url: content.learn.url,
+      UTMTerm: props.UTMTerm
+    }); // By default we show accounts section on top and
+    // cards section in bottom half of the full page interrupt
+
+    const cardsFirst = content && content.className === "fullPageCardsAtTop";
+    const firstContainerClassName = ["container", content && content.className].join(" ");
+    return react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+      className: "fullpage-wrapper"
+    }, react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+      className: "fullpage-icon brand-logo"
+    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h1", {
+      className: "welcome-title",
+      "data-l10n-id": "onboarding-welcome-header"
+    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("h2", {
+      className: "welcome-subtitle",
+      "data-l10n-id": "onboarding-fullpage-welcome-subheader"
+    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+      className: firstContainerClassName
+    }, cardsFirst ? cards : accounts), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+      className: "section-divider"
+    }), react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement("div", {
+      className: "container"
+    }, cardsFirst ? accounts : cards));
+  }
+
+}
+FullPageInterrupt.defaultProps = {
+  flowParams: {
+    deviceId: "",
+    flowId: "",
+    flowBeginTime: ""
+  }
+};
+/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
+
+/***/ }),
+/* 26 */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OnboardingCard", function() { return OnboardingCard; });
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
+/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+class OnboardingCard extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
+  constructor(props) {
+    super(props);
+    this.onClick = this.onClick.bind(this);
+  }
+
+  onClick() {
+    const {
+      props
+    } = this;
+    const ping = {
+      event: "CLICK_BUTTON",
+      message_id: props.id,
+      id: props.UISurface
+    };
+    props.sendUserActionTelemetry(ping);
+    props.onAction(props.content.primary_button.action);
+  }
+
+  render() {
+    const {
+      content
+    } = this.props;
+    const className = this.props.className || "onboardingMessage";
+    return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+      className: className
+    }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+      className: `onboardingMessageImage ${content.icon}`
+    }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
+      className: "onboardingContent"
+    }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", {
+      className: "onboardingTitle",
+      "data-l10n-id": content.title.string_id
+    }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
+      className: "onboardingText",
+      "data-l10n-id": content.text.string_id
+    })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
+      className: "onboardingButtonContainer"
+    }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
+      "data-l10n-id": content.primary_button.label.string_id,
+      className: "button onboardingButton",
+      onClick: this.onClick
+    }))));
+  }
+
+}
+
+/***/ }),
+/* 27 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Triplets", function() { return Triplets; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
-/* harmony import */ var _templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25);
-/* harmony import */ var _addUtmParams__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(22);
+/* harmony import */ var _templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(26);
+/* harmony import */ var _addUtmParams__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(23);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -3322,83 +3642,23 @@ class Triplets extends react__WEBPACK_IM
       "data-l10n-id": "onboarding-cards-dismiss"
     })));
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 25 */
-/***/ (function(module, __webpack_exports__, __webpack_require__) {
-
-"use strict";
-__webpack_require__.r(__webpack_exports__);
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OnboardingCard", function() { return OnboardingCard; });
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
-/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-class OnboardingCard extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
-  constructor(props) {
-    super(props);
-    this.onClick = this.onClick.bind(this);
-  }
-
-  onClick() {
-    const {
-      props
-    } = this;
-    const ping = {
-      event: "CLICK_BUTTON",
-      message_id: props.id,
-      id: props.UISurface
-    };
-    props.sendUserActionTelemetry(ping);
-    props.onAction(props.content.primary_button.action);
-  }
-
-  render() {
-    const {
-      content
-    } = this.props;
-    const className = this.props.className || "onboardingMessage";
-    return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
-      className: className
-    }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
-      className: `onboardingMessageImage ${content.icon}`
-    }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
-      className: "onboardingContent"
-    }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", null, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", {
-      className: "onboardingTitle",
-      "data-l10n-id": content.title.string_id
-    }), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
-      className: "onboardingText",
-      "data-l10n-id": content.text.string_id
-    })), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
-      className: "onboardingButtonContainer"
-    }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
-      "data-l10n-id": content.primary_button.label.string_id,
-      className: "button onboardingButton",
-      onClick: this.onClick
-    }))));
-  }
-
-}
-
-/***/ }),
-/* 26 */
+/* 28 */
 /***/ (function(module, exports) {
 
 module.exports = ReactRedux;
 
 /***/ }),
-/* 27 */
+/* 29 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleHashRouter", function() { return SimpleHashRouter; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
@@ -3437,25 +3697,25 @@ class SimpleHashRouter extends react__WE
       }
     });
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 28 */
+/* 30 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_ConfirmDialog", function() { return _ConfirmDialog; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ConfirmDialog", function() { return ConfirmDialog; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(26);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
@@ -3542,28 +3802,28 @@ class _ConfirmDialog extends react__WEBP
       "data-l10n-id": this.props.data.confirm_button_string_id
     }))));
   }
 
 }
 const ConfirmDialog = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])(state => state.Dialog)(_ConfirmDialog);
 
 /***/ }),
-/* 29 */
+/* 31 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_LinkMenu", function() { return _LinkMenu; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "LinkMenu", function() { return LinkMenu; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(26);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(30);
-/* harmony import */ var content_src_lib_link_menu_options__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(31);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(32);
+/* harmony import */ var content_src_lib_link_menu_options__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(33);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_4__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -3637,17 +3897,17 @@ class _LinkMenu extends react__WEBPACK_I
 const getState = state => ({
   isPrivateBrowsingEnabled: state.Prefs.values.isPrivateBrowsingEnabled,
   platform: state.Prefs.values.platform
 });
 
 const LinkMenu = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])(getState)(_LinkMenu);
 
 /***/ }),
-/* 30 */
+/* 32 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ContextMenu", function() { return ContextMenu; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ContextMenuItem", function() { return ContextMenuItem; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
@@ -3815,17 +4075,17 @@ class ContextMenuItem extends react__WEB
       "data-l10n-id": option.string_id || option.id
     })));
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 31 */
+/* 33 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "LinkMenuOptions", function() { return LinkMenuOptions; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -3858,17 +4118,18 @@ const LinkMenuOptions = {
   EmptyItem: () => ({
     type: "empty"
   }),
   ShowPrivacyInfo: site => ({
     id: "newtab-menu-show-privacy-info",
     icon: "info",
     action: {
       type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].SHOW_PRIVACY_INFO
-    }
+    },
+    userEvent: "SHOW_PRIVACY_INFO"
   }),
   RemoveBookmark: site => ({
     id: "newtab-menu-remove-bookmark",
     icon: "bookmark-added",
     action: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].AlsoToMain({
       type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].DELETE_BOOKMARK_BY_ID,
       data: site.bookmarkGuid
     }),
@@ -3895,24 +4156,31 @@ const LinkMenuOptions = {
       data: {
         referrer: site.referrer,
         typedBonus: site.typedBonus,
         url: site.url
       }
     }),
     userEvent: "OPEN_NEW_WINDOW"
   }),
+  // This blocks the url for regular stories,
+  // but also sends a message to DiscoveryStream with campaign_id.
+  // If DiscoveryStream sees this message for a campaign_id
+  // it also blocks it on the campaign_id.
   BlockUrl: (site, index, eventSource) => ({
     id: "newtab-menu-dismiss",
     icon: "dismiss",
     action: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].AlsoToMain({
       type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionTypes"].BLOCK_URL,
       data: {
         url: site.open_url || site.url,
-        pocket_id: site.pocket_id
+        pocket_id: site.pocket_id,
+        ...(site.campaign_id ? {
+          campaign_id: site.campaign_id
+        } : {})
       }
     }),
     impression: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["actionCreators"].ImpressionStats({
       source: eventSource,
       block: 0,
       tiles: [{
         id: site.guid,
         pos: index,
@@ -4120,17 +4388,17 @@ const LinkMenuOptions = {
   CheckBookmark: site => site.bookmarkGuid ? LinkMenuOptions.RemoveBookmark(site) : LinkMenuOptions.AddBookmark(site),
   CheckPinTopSite: (site, index) => site.isPinned ? LinkMenuOptions.UnpinTopSite(site) : LinkMenuOptions.PinTopSite(site, index),
   CheckSavedToPocket: (site, index) => site.pocket_id ? LinkMenuOptions.DeleteFromPocket(site) : LinkMenuOptions.SaveToPocket(site, index),
   CheckBookmarkOrArchive: site => site.pocket_id ? LinkMenuOptions.ArchiveFromPocket(site) : LinkMenuOptions.CheckBookmark(site),
   OpenInPrivateWindow: (site, index, eventSource, isEnabled) => isEnabled ? _OpenInPrivateWindow(site) : LinkMenuOptions.EmptyItem()
 };
 
 /***/ }),
-/* 32 */
+/* 34 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ContextMenuButton", function() { return ContextMenuButton; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
@@ -4205,17 +4473,17 @@ class ContextMenuButton extends react__W
       keyboardAccess: contextMenuKeyboard,
       onUpdate: this.onUpdate
     }) : null);
   }
 
 }
 
 /***/ }),
-/* 33 */
+/* 35 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "INTERSECTION_RATIO", function() { return INTERSECTION_RATIO; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ImpressionStats", function() { return ImpressionStats; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
@@ -4427,17 +4695,17 @@ ImpressionStats.defaultProps = {
   IntersectionObserver: global.IntersectionObserver,
   document: global.document,
   rows: [],
   source: ""
 };
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 34 */
+/* 36 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "cardContextTypes", function() { return cardContextTypes; });
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -4464,36 +4732,36 @@ const cardContextTypes = {
   },
   download: {
     fluentID: "newtab-label-download",
     icon: "download"
   }
 };
 
 /***/ }),
-/* 35 */
+/* 37 */
 /***/ (function(module, exports) {
 
 module.exports = ReactTransitionGroup;
 
 /***/ }),
-/* 36 */
+/* 38 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CollapsibleSection", function() { return CollapsibleSection; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(37);
-/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(39);
+/* harmony import */ var content_src_components_ErrorBoundary_ErrorBoundary__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(39);
+/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(41);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
-/* harmony import */ var content_src_components_SectionMenu_SectionMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(40);
-/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(41);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(32);
+/* harmony import */ var content_src_components_SectionMenu_SectionMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42);
+/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(43);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(34);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
 
 
@@ -4759,24 +5027,24 @@ CollapsibleSection.defaultProps = {
   },
   Prefs: {
     values: {}
   }
 };
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 37 */
+/* 39 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ErrorBoundaryFallback", function() { return ErrorBoundaryFallback; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ErrorBoundary", function() { return ErrorBoundary; });
-/* harmony import */ var content_src_components_A11yLinkButton_A11yLinkButton__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(38);
+/* harmony import */ var content_src_components_A11yLinkButton_A11yLinkButton__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(40);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 class ErrorBoundaryFallback extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureComponent {
@@ -4846,17 +5114,17 @@ class ErrorBoundary extends react__WEBPA
   }
 
 }
 ErrorBoundary.defaultProps = {
   FallbackComponent: ErrorBoundaryFallback
 };
 
 /***/ }),
-/* 38 */
+/* 40 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "A11yLinkButton", function() { return A11yLinkButton; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
@@ -4876,17 +5144,17 @@ function A11yLinkButton(props) {
   return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", _extends({
     type: "button"
   }, props, {
     className: className
   }), props.children);
 }
 
 /***/ }),
-/* 39 */
+/* 41 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FluentOrText", function() { return FluentOrText; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
@@ -4922,28 +5190,28 @@ class FluentOrText extends react__WEBPAC
 
 
     return react__WEBPACK_IMPORTED_MODULE_0___default.a.cloneElement(child, extraProps, grandChildren);
   }
 
 }
 
 /***/ }),
-/* 40 */
+/* 42 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_SectionMenu", function() { return _SectionMenu; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SectionMenu", function() { return SectionMenu; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenu__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(32);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(41);
+/* harmony import */ var content_src_lib_section_menu_options__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(43);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
 
 const DEFAULT_SECTION_MENU_OPTIONS = ["MoveUp", "MoveDown", "Separator", "RemoveSection", "CheckCollapsed", "Separator", "ManageSection"];
@@ -5028,17 +5296,17 @@ class _SectionMenu extends react__WEBPAC
       keyboardAccess: this.props.keyboardAccess
     });
   }
 
 }
 const SectionMenu = _SectionMenu;
 
 /***/ }),
-/* 41 */
+/* 43 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SectionMenuOptions", function() { return SectionMenuOptions; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -5158,38 +5426,38 @@ const SectionMenuOptions = {
       }
     }),
     userEvent: "MENU_PRIVACY_NOTICE"
   }),
   CheckCollapsed: section => section.collapsed ? SectionMenuOptions.ExpandSection(section) : SectionMenuOptions.CollapseSection(section)
 };
 
 /***/ }),
-/* 42 */
+/* 44 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Section", function() { return Section; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SectionIntl", function() { return SectionIntl; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Sections", function() { return _Sections; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Sections", function() { return Sections; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var content_src_components_Card_Card__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(43);
-/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(36);
-/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(45);
-/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(39);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(26);
+/* harmony import */ var content_src_components_Card_Card__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45);
+/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(38);
+/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(47);
+/* harmony import */ var content_src_components_FluentOrText_FluentOrText__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(41);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var content_src_components_MoreRecommendations_MoreRecommendations__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(47);
-/* harmony import */ var content_src_components_PocketLoggedInCta_PocketLoggedInCta__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(48);
+/* harmony import */ var content_src_components_MoreRecommendations_MoreRecommendations__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(49);
+/* harmony import */ var content_src_components_PocketLoggedInCta_PocketLoggedInCta__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(50);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_8__);
-/* harmony import */ var content_src_components_Topics_Topics__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(49);
-/* harmony import */ var content_src_components_TopSites_TopSites__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(50);
+/* harmony import */ var content_src_components_Topics_Topics__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(51);
+/* harmony import */ var content_src_components_TopSites_TopSites__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(52);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -5529,33 +5797,33 @@ class _Sections extends react__WEBPACK_I
 }
 const Sections = Object(react_redux__WEBPACK_IMPORTED_MODULE_5__["connect"])(state => ({
   Sections: state.Sections,
   Prefs: state.Prefs
 }))(_Sections);
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 43 */
+/* 45 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Card", function() { return _Card; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Card", function() { return Card; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PlaceholderCard", function() { return PlaceholderCard; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(26);
+/* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(36);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_2__);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(32);
-/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(29);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(34);
+/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(31);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_5__);
-/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(44);
+/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(46);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
 
 
@@ -5874,17 +6142,17 @@ const Card = Object(react_redux__WEBPACK
   platform: state.Prefs.values.platform
 }))(_Card);
 const PlaceholderCard = props => react__WEBPACK_IMPORTED_MODULE_5___default.a.createElement(Card, {
   placeholder: true,
   className: props.className
 });
 
 /***/ }),
-/* 44 */
+/* 46 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ScreenshotUtils", function() { return ScreenshotUtils; });
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -5943,24 +6211,24 @@ const ScreenshotUtils = {
 
     return !remoteImage && !localImage;
   }
 
 };
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 45 */
+/* 47 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ComponentPerfTimer", function() { return ComponentPerfTimer; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(46);
+/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(48);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_2__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
  // Currently record only a fixed set of sections. This will prevent data
@@ -6123,17 +6391,17 @@ class ComponentPerfTimer extends react__
     }
 
     return this.props.children;
   }
 
 }
 
 /***/ }),
-/* 46 */
+/* 48 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_PerfService", function() { return _PerfService; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "perfService", function() { return perfService; });
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
@@ -6255,17 +6523,17 @@ function _PerfService(options) {
     let mostRecentEntry = entries[entries.length - 1];
     return this._perf.timeOrigin + mostRecentEntry.startTime;
   }
 
 };
 var perfService = new _PerfService();
 
 /***/ }),
-/* 47 */
+/* 49 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MoreRecommendations", function() { return MoreRecommendations; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
@@ -6287,24 +6555,24 @@ class MoreRecommendations extends react_
     }
 
     return null;
   }
 
 }
 
 /***/ }),
-/* 48 */
+/* 50 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_PocketLoggedInCta", function() { return _PocketLoggedInCta; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PocketLoggedInCta", function() { return PocketLoggedInCta; });
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(26);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
@@ -6330,17 +6598,17 @@ class _PocketLoggedInCta extends react__
   }
 
 }
 const PocketLoggedInCta = Object(react_redux__WEBPACK_IMPORTED_MODULE_0__["connect"])(state => ({
   Pocket: state.Pocket
 }))(_PocketLoggedInCta);
 
 /***/ }),
-/* 49 */
+/* 51 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Topic", function() { return Topic; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Topics", function() { return Topics; });
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
@@ -6375,36 +6643,36 @@ class Topics extends react__WEBPACK_IMPO
       url: t.url,
       name: t.name
     }))));
   }
 
 }
 
 /***/ }),
-/* 50 */
+/* 52 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_TopSites", function() { return _TopSites; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSites", function() { return TopSites; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(51);
-/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(36);
-/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(45);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(26);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(53);
+/* harmony import */ var content_src_components_CollapsibleSection_CollapsibleSection__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(38);
+/* harmony import */ var content_src_components_ComponentPerfTimer_ComponentPerfTimer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(47);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_4__);
 /* harmony import */ var _asrouter_components_ModalOverlay_ModalOverlay__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(21);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_6__);
-/* harmony import */ var _SearchShortcutsForm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(52);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(60);
-/* harmony import */ var _TopSiteForm__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(62);
-/* harmony import */ var _TopSite__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(53);
+/* harmony import */ var _SearchShortcutsForm__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(54);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(62);
+/* harmony import */ var _TopSiteForm__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(64);
+/* harmony import */ var _TopSite__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(55);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -6602,17 +6870,17 @@ const TopSites = Object(react_redux__WEB
   // For SPOC Experiment only, take TopSites from DiscoveryStream TopSites that takes in SPOC Data
   TopSites: props.TopSitesWithSpoc || state.TopSites,
   Prefs: state.Prefs,
   TopSitesRows: state.Prefs.values.topSitesRows
 }))(_TopSites);
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 51 */
+/* 53 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_SOURCE", function() { return TOP_SITES_SOURCE; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_CONTEXT_MENU_OPTIONS", function() { return TOP_SITES_CONTEXT_MENU_OPTIONS; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_SPOC_CONTEXT_MENU_OPTIONS", function() { return TOP_SITES_SPOC_CONTEXT_MENU_OPTIONS; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS", function() { return TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS; });
@@ -6627,27 +6895,27 @@ const TOP_SITES_SPOC_CONTEXT_MENU_OPTION
 
 const TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "BlockUrl"]; // minimum size necessary to show a rich icon instead of a screenshot
 
 const MIN_RICH_FAVICON_SIZE = 96; // minimum size necessary to show any icon in the top left corner with a screenshot
 
 const MIN_CORNER_FAVICON_SIZE = 16;
 
 /***/ }),
-/* 52 */
+/* 54 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SelectableSearchShortcut", function() { return SelectableSearchShortcut; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SearchShortcutsForm", function() { return SearchShortcutsForm; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(51);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(53);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
 class SelectableSearchShortcut extends react__WEBPACK_IMPORTED_MODULE_1___default.a.PureComponent {
   render() {
@@ -6818,34 +7086,34 @@ class SearchShortcutsForm extends react_
       onClick: this.onSaveButtonClick,
       "data-l10n-id": "newtab-topsites-save-button"
     })));
   }
 
 }
 
 /***/ }),
-/* 53 */
+/* 55 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSiteLink", function() { return TopSiteLink; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSite", function() { return TopSite; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSitePlaceholder", function() { return TopSitePlaceholder; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSiteList", function() { return TopSiteList; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(51);
-/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29);
-/* harmony import */ var _DiscoveryStreamImpressionStats_ImpressionStats__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(33);
+/* harmony import */ var _TopSitesConstants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(53);
+/* harmony import */ var content_src_components_LinkMenu_LinkMenu__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31);
+/* harmony import */ var _DiscoveryStreamImpressionStats_ImpressionStats__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(35);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_4__);
-/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(44);
-/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(60);
-/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(32);
+/* harmony import */ var content_src_lib_screenshot_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(46);
+/* harmony import */ var common_Reducers_jsm__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(62);
+/* harmony import */ var content_src_components_ContextMenu_ContextMenuButton__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(34);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -7495,25 +7763,25 @@ class TopSiteList extends react__WEBPACK
     return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("ul", {
       className: `top-sites-list${this.state.draggedSite ? " dnd-active" : ""}`
     }, topSitesUI);
   }
 
 }
 
 /***/ }),
-/* 54 */
+/* 56 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "_Search", function() { return _Search; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Search", function() { return Search; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(26);
+/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(28);
 /* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_redux__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var content_src_lib_constants__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_3__);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
@@ -7688,24 +7956,24 @@ class _Search extends react__WEBPACK_IMP
 
 const getState = state => ({
   permanentPrivateBrowsing: state.App.permanentPrivateBrowsing
 });
 
 const Search = Object(react_redux__WEBPACK_IMPORTED_MODULE_1__["connect"])(getState)(_Search);
 
 /***/ }),
-/* 55 */
+/* 57 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* WEBPACK VAR INJECTION */(function(global) {/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DetectUserSessionStart", function() { return DetectUserSessionStart; });
 /* harmony import */ var common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
-/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(46);
+/* harmony import */ var common_PerfService_jsm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(48);
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 const VISIBLE = "visible";
 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
 class DetectUserSessionStart {
@@ -7770,17 +8038,17 @@ class DetectUserSessionStart {
       this.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
     }
   }
 
 }
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(1)))
 
 /***/ }),
-/* 56 */
+/* 58 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 
 // EXTERNAL MODULE: ./common/Actions.jsm
 var Actions = __webpack_require__(2);
 
@@ -7843,31 +8111,35 @@ class DSImage_DSImage extends external_R
   reformatImageURL(url, width, height) {
     // Change the image URL to request a size tailored for the parent container width
     // Also: force JPEG, quality 60, no upscaling, no EXIF data
     // Uses Thumbor: https://thumbor.readthedocs.io/en/latest/usage.html
     return `https://img-getpocket.cdn.mozilla.net/${width}x${height}/filters:format(jpeg):quality(60):no_upscale():strip_exif()/${encodeURIComponent(url)}`;
   }
 
   componentDidMount() {
-    this.idleCallbackId = window.requestIdleCallback(this.onIdleCallback.bind(this));
+    this.idleCallbackId = this.props.windowObj.requestIdleCallback(this.onIdleCallback.bind(this));
     this.observer = new IntersectionObserver(this.onSeen.bind(this), {
       // Assume an image will be eventually seen if it is within
       // half the average Desktop vertical screen size:
       // http://gs.statcounter.com/screen-resolution-stats/desktop/north-america
       rootMargin: `540px`
     });
     this.observer.observe(external_ReactDOM_default.a.findDOMNode(this));
   }
 
   componentWillUnmount() {
     // Remove observer on unmount
     if (this.observer) {
       this.observer.unobserve(external_ReactDOM_default.a.findDOMNode(this));
     }
+
+    if (this.idleCallbackId) {
+      this.props.windowObj.cancelIdleCallback(this.idleCallbackId);
+    }
   }
 
   render() {
     let classNames = `ds-image
       ${this.props.extraClassNames ? ` ${this.props.extraClassNames}` : ``}
       ${this.state && this.state.useTransition ? ` use-transition` : ``}
       ${this.state && this.state.isSeen ? ` loaded` : ``}
     `;
@@ -7928,23 +8200,25 @@ DSImage_DSImage.defaultProps = {
   source: null,
   // The current source style from Pocket API (always 450px)
   rawSource: null,
   // Unadulterated image URL to filter through Thumbor
   extraClassNames: null,
   // Additional classnames to append to component
   optimize: true,
   // Measure parent container to request exact sizes
-  alt_text: null
+  alt_text: null,
+  windowObj: window // Added to support unit tests
+
 };
 // EXTERNAL MODULE: ./content-src/components/LinkMenu/LinkMenu.jsx
-var LinkMenu = __webpack_require__(29);
+var LinkMenu = __webpack_require__(31);
 
 // EXTERNAL MODULE: ./content-src/components/ContextMenu/ContextMenuButton.jsx
-var ContextMenuButton = __webpack_require__(32);
+var ContextMenuButton = __webpack_require__(34);
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -8004,28 +8278,29 @@ class DSLinkMenu_DSLinkMenu extends exte
       site: {
         referrer: "https://getpocket.com/recommendations",
         title: this.props.title,
         type: this.props.type,
         url: this.props.url,
         guid: this.props.id,
         pocket_id: this.props.pocket_id,
         shim: this.props.shim,
-        bookmarkGuid: this.props.bookmarkGuid
+        bookmarkGuid: this.props.bookmarkGuid,
+        campaign_id: this.props.campaignId
       }
     })));
   }
 
 }
 DSLinkMenu_DSLinkMenu.defaultProps = {
   windowObj: window // Added to support unit tests
 
 };
 // EXTERNAL MODULE: ./content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
-var ImpressionStats = __webpack_require__(33);
+var ImpressionStats = __webpack_require__(35);
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 class SafeAnchor_SafeAnchor extends external_React_default.a.PureComponent {
@@ -8097,20 +8372,20 @@ class SafeAnchor_SafeAnchor extends exte
       href: this.safeURI(url),
       className: className,
       onClick: this.onClick
     }, this.props.children);
   }
 
 }
 // EXTERNAL MODULE: ./content-src/components/Card/types.js
-var types = __webpack_require__(34);
+var types = __webpack_require__(36);
 
 // EXTERNAL MODULE: external "ReactTransitionGroup"
-var external_ReactTransitionGroup_ = __webpack_require__(35);
+var external_ReactTransitionGroup_ = __webpack_require__(37);
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
  // Animation time is mirrored in DSContextFooter.scss
@@ -8372,17 +8647,18 @@ class DSCard_DSCard extends external_Rea
       index: this.props.pos,
       dispatch: this.props.dispatch,
       url: this.props.url,
       title: this.props.title,
       source: this.props.source,
       type: this.props.type,
       pocket_id: this.props.pocket_id,
       shim: this.props.shim,
-      bookmarkGuid: this.props.bookmarkGuid
+      bookmarkGuid: this.props.bookmarkGuid,
+      campaignId: this.props.campaignId
     }));
   }
 
 }
 DSCard_DSCard.defaultProps = {
   windowObj: window // Added to support unit tests
 
 };
@@ -8562,23 +8838,23 @@ class CardGrid_CardGrid extends external
 
 }
 CardGrid_CardGrid.defaultProps = {
   border: `border`,
   items: 4 // Number of stories to display
 
 };
 // EXTERNAL MODULE: ./content-src/components/CollapsibleSection/CollapsibleSection.jsx
-var CollapsibleSection = __webpack_require__(36);
+var CollapsibleSection = __webpack_require__(38);
 
 // EXTERNAL MODULE: external "ReactRedux"
-var external_ReactRedux_ = __webpack_require__(26);
+var external_ReactRedux_ = __webpack_require__(28);
 
 // EXTERNAL MODULE: ./content-src/lib/link-menu-options.js
-var link_menu_options = __webpack_require__(31);
+var link_menu_options = __webpack_require__(33);
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss.jsx
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -8677,20 +8953,29 @@ var ModalOverlay = __webpack_require__(2
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal.jsx
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
+
 class DSPrivacyModal_DSPrivacyModal extends external_React_default.a.PureComponent {
   constructor(props) {
     super(props);
     this.closeModal = this.closeModal.bind(this);
+    this.onLinkClick = this.onLinkClick.bind(this);
+  }
+
+  onLinkClick(event) {
+    this.props.dispatch(Actions["actionCreators"].UserEvent({
+      event: "CLICK_PRIVACY_INFO",
+      source: "DS_PRIVACY_MODAL"
+    }));
   }
 
   closeModal() {
     this.props.dispatch({
       type: `HIDE_PRIVACY_INFO`,
       data: {}
     });
   }
@@ -8701,16 +8986,17 @@ class DSPrivacyModal_DSPrivacyModal exte
       innerClassName: "ds-privacy-modal"
     }, external_React_default.a.createElement("div", {
       className: "privacy-notice"
     }, external_React_default.a.createElement("h3", {
       "data-l10n-id": "newtab-privacy-modal-header"
     }), external_React_default.a.createElement("p", {
       "data-l10n-id": "newtab-privacy-modal-paragraph"
     }), external_React_default.a.createElement(SafeAnchor_SafeAnchor, {
+      onLinkClick: this.onLinkClick,
       url: "https://www.mozilla.org/en-US/privacy/firefox/"
     }, external_React_default.a.createElement("span", {
       "data-l10n-id": "newtab-privacy-modal-link"
     }))), external_React_default.a.createElement("section", {
       className: "actions"
     }, external_React_default.a.createElement("button", {
       className: "done",
       type: "submit",
@@ -8873,17 +9159,18 @@ class List_ListItem extends external_Rea
       index: this.props.pos,
       dispatch: this.props.dispatch,
       url: this.props.url,
       title: this.props.title,
       source: this.props.source,
       type: this.props.type,
       pocket_id: this.props.pocket_id,
       shim: this.props.shim,
-      bookmarkGuid: this.props.bookmarkGuid
+      bookmarkGuid: this.props.bookmarkGuid,
+      campaignId: this.props.campaignId
     }));
   }
 
 }
 const PlaceholderListItem = props => external_React_default.a.createElement(List_ListItem, {
   placeholder: true
 });
 /**
@@ -9084,17 +9371,18 @@ class Hero_Hero extends external_React_d
         index: heroRec.pos,
         dispatch: this.props.dispatch,
         url: heroRec.url,
         title: heroRec.title,
         source: heroRec.domain,
         type: this.props.type,
         pocket_id: heroRec.pocket_id,
         shim: heroRec.shim,
-        bookmarkGuid: heroRec.bookmarkGuid
+        bookmarkGuid: heroRec.bookmarkGuid,
+        campaignId: heroRec.campaign_id
       }));
     }
 
     let list = external_React_default.a.createElement(List, {
       recStartingPoint: 1,
       data: this.props.data,
       feed: this.props.feed,
       hasImages: true,
@@ -9134,17 +9422,17 @@ class Hero_Hero extends external_React_d
 }
 Hero_Hero.defaultProps = {
   data: {},
   border: `border`,
   items: 1 // Number of stories to display
 
 };
 // EXTERNAL MODULE: ./content-src/components/Sections/Sections.jsx
-var Sections = __webpack_require__(42);
+var Sections = __webpack_require__(44);
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/Highlights/Highlights.jsx
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
@@ -9478,20 +9766,20 @@ const selectLayoutRender = (state, prefs
   }
 
   return {
     spocsFill,
     layoutRender
   };
 };
 // EXTERNAL MODULE: ./content-src/components/TopSites/TopSites.jsx
-var TopSites_TopSites = __webpack_require__(50);
+var TopSites_TopSites = __webpack_require__(52);
 
 // EXTERNAL MODULE: ./common/Reducers.jsm + 1 modules
-var Reducers = __webpack_require__(60);
+var Reducers = __webpack_require__(62);
 
 // CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/TopSites/TopSites.jsx
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
@@ -9986,17 +10274,17 @@ class DiscoveryStreamBase_DiscoveryStrea
 }
 const DiscoveryStreamBase = Object(external_ReactRedux_["connect"])(state => ({
   DiscoveryStream: state.DiscoveryStream,
   Prefs: state.Prefs,
   Sections: state.Sections
 }))(DiscoveryStreamBase_DiscoveryStreamBase);
 
 /***/ }),
-/* 57 */
+/* 59 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 
 // EXTERNAL MODULE: external "React"
 var external_React_ = __webpack_require__(9);
 
@@ -10783,17 +11071,17 @@ localized_Localized.propTypes = {
  * components for more information.
  */
 
 
 
 
 
 /***/ }),
-/* 58 */
+/* 60 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 
 // EXTERNAL MODULE: external "React"
 var external_React_ = __webpack_require__(9);
 var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
@@ -11952,17 +12240,17 @@ const SnippetsTemplates = {
   newsletter_snippet: NewsletterSnippet,
   fxa_signup_snippet: FXASignupSnippet,
   send_to_device_snippet: SendToDeviceSnippet,
   eoy_snippet: EOYSnippet,
   simple_below_search_snippet: SimpleBelowSearchSnippet_SimpleBelowSearchSnippet
 };
 
 /***/ }),
-/* 59 */
+/* 61 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 
 // CONCATENATED MODULE: ./node_modules/fluent/src/types.js
 /* global Intl */
 
@@ -13372,17 +13660,17 @@ function generateBundles(content) {
     }
 
     bundle.addMessages(`${key} = ${string}`);
   });
   return [bundle];
 }
 
 /***/ }),
-/* 60 */
+/* 62 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 
 // EXTERNAL MODULE: ./common/Actions.jsm
 var Actions = __webpack_require__(2);
 
@@ -14287,52 +14575,57 @@ var reducers = {
   Dialog,
   Sections,
   Pocket,
   DiscoveryStream,
   Search
 };
 
 /***/ }),
-/* 61 */
+/* 63 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 
 // EXTERNAL MODULE: external "React"
 var external_React_ = __webpack_require__(9);
 var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
 
 // EXTERNAL MODULE: ./content-src/asrouter/templates/Trailhead/Trailhead.jsx
 var Trailhead = __webpack_require__(20);
 
 // EXTERNAL MODULE: ./content-src/asrouter/templates/ReturnToAMO/ReturnToAMO.jsx
-var ReturnToAMO = __webpack_require__(23);
+var ReturnToAMO = __webpack_require__(24);
+
+// EXTERNAL MODULE: ./content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt.jsx
+var FullPageInterrupt = __webpack_require__(25);
 
 // EXTERNAL MODULE: ./node_modules/fluent-react/src/index.js + 14 modules
-var src = __webpack_require__(57);
+var src = __webpack_require__(59);
 
 // EXTERNAL MODULE: ./content-src/asrouter/rich-text-strings.js + 8 modules
-var rich_text_strings = __webpack_require__(59);
+var rich_text_strings = __webpack_require__(61);
 
 // CONCATENATED MODULE: ./content-src/asrouter/templates/FirstRun/Interrupt.jsx
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 
 
 
+
 class Interrupt_Interrupt extends external_React_default.a.PureComponent {
   render() {
     const {
+      cards,
       onDismiss,
       onNextScene,
       message,
       sendUserActionTelemetry,
       executeAction,
       dispatch,
       fxaEndpoint,
       UTMTerm,
@@ -14348,16 +14641,30 @@ class Interrupt_Interrupt extends extern
         }, external_React_default.a.createElement(ReturnToAMO["ReturnToAMO"], _extends({}, message, {
           document: this.props.document,
           UISurface: "NEWTAB_OVERLAY",
           onBlock: onDismiss,
           onAction: executeAction,
           sendUserActionTelemetry: sendUserActionTelemetry
         })));
 
+      case "full_page_interrupt":
+        return external_React_default.a.createElement(FullPageInterrupt["FullPageInterrupt"], {
+          document: this.props.document,
+          cards: cards,
+          message: message,
+          onBlock: onDismiss,
+          onAction: executeAction,
+          dispatch: dispatch,
+          fxaEndpoint: fxaEndpoint,
+          sendUserActionTelemetry: sendUserActionTelemetry,
+          UTMTerm: UTMTerm,
+          flowParams: flowParams
+        });
+
       case "trailhead":
         return external_React_default.a.createElement(Trailhead["Trailhead"], {
           document: this.props.document,
           message: message,
           onNextScene: onNextScene,
           onAction: executeAction,
           sendUserActionTelemetry: sendUserActionTelemetry,
           dispatch: dispatch,
@@ -14368,20 +14675,20 @@ class Interrupt_Interrupt extends extern
 
       default:
         throw new Error(`${message.template} is not a valid FirstRun message`);
     }
   }
 
 }
 // EXTERNAL MODULE: ./content-src/asrouter/templates/FirstRun/Triplets.jsx
-var Triplets = __webpack_require__(24);
+var Triplets = __webpack_require__(27);
 
 // EXTERNAL MODULE: ./content-src/asrouter/templates/FirstRun/addUtmParams.js
-var addUtmParams = __webpack_require__(22);
+var addUtmParams = __webpack_require__(23);
 
 // CONCATENATED MODULE: ./content-src/asrouter/templates/FirstRun/FirstRun.jsx
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FLUENT_FILES", function() { return FLUENT_FILES; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "helpers", function() { return helpers; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FirstRun", function() { return FirstRun_FirstRun; });
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -14542,16 +14849,17 @@ class FirstRun_FirstRun extends external
       isTripletsContainerVisible,
       isTripletsContentVisible,
       hasTriplets,
       UTMTerm,
       flowParams
     } = this.state;
     return external_React_default.a.createElement(external_React_default.a.Fragment, null, isInterruptVisible ? external_React_default.a.createElement(Interrupt_Interrupt, {
       document: props.document,
+      cards: triplets,
       message: interrupt,
       onNextScene: this.closeInterrupt,
       UTMTerm: UTMTerm,
       sendUserActionTelemetry: sendUserActionTelemetry,
       executeAction: executeAction,
       dispatch: dispatch,
       flowParams: flowParams,
       onDismiss: this.closeInterrupt,
@@ -14567,34 +14875,34 @@ class FirstRun_FirstRun extends external
       flowParams: flowParams,
       onAction: executeAction
     }) : null);
   }
 
 }
 
 /***/ }),
-/* 62 */
+/* 64 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 
 // EXTERNAL MODULE: ./common/Actions.jsm
 var Actions = __webpack_require__(2);
 
 // EXTERNAL MODULE: ./content-src/components/A11yLinkButton/A11yLinkButton.jsx
-var A11yLinkButton = __webpack_require__(38);
+var A11yLinkButton = __webpack_require__(40);
 
 // EXTERNAL MODULE: external "React"
 var external_React_ = __webpack_require__(9);
 var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_);
 
 // EXTERNAL MODULE: ./content-src/components/TopSites/TopSitesConstants.js
-var TopSitesConstants = __webpack_require__(51);
+var TopSitesConstants = __webpack_require__(53);
 
 // CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteFormInput.jsx
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 class TopSiteFormInput_TopSiteFormInput extends external_React_default.a.PureComponent {
   constructor(props) {
@@ -14699,17 +15007,17 @@ class TopSiteFormInput_TopSiteFormInput 
 
 }
 TopSiteFormInput_TopSiteFormInput.defaultProps = {
   showClearButton: false,
   value: "",
   validationError: false
 };
 // EXTERNAL MODULE: ./content-src/components/TopSites/TopSite.jsx
-var TopSite = __webpack_require__(53);
+var TopSite = __webpack_require__(55);
 
 // CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteForm.jsx
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TopSiteForm", function() { return TopSiteForm_TopSiteForm; });
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..99f7c9ee72fcf853f487e55156e60fa11ab6cc2a
GIT binary patch
literal 17840
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
Tl)%3=00000NkvXXu0mjf#DmWn
--- a/browser/components/newtab/docs/v2-system-addon/data_events.md
+++ b/browser/components/newtab/docs/v2-system-addon/data_events.md
@@ -1,11 +1,11 @@
 # Metrics we collect
 
-By default, the about:newtab and about:home pages in Firefox (the pages you see when you open a new tab and when you start the browser), will send data back to Mozilla servers about usage of these pages.  The intent is to collect data in order to improve the user's experience while using Activity Stream.  Data about your specific browsing behaior or the sites you visit is **never transmitted to any Mozilla server**.  At any time, it is easy to **turn off** this data collection by [opting out of Firefox telemetry](https://support.mozilla.org/kb/share-telemetry-data-mozilla-help-improve-firefox).
+By default, the about:newtab, about:welcome and about:home pages in Firefox (the pages you see when you open a new tab and when you start the browser), will send data back to Mozilla servers about usage of these pages.  The intent is to collect data in order to improve the user's experience while using Activity Stream.  Data about your specific browsing behaior or the sites you visit is **never transmitted to any Mozilla server**.  At any time, it is easy to **turn off** this data collection by [opting out of Firefox telemetry](https://support.mozilla.org/kb/share-telemetry-data-mozilla-help-improve-firefox).
 
 Data is sent to our servers in the form of discreet HTTPS 'pings' or messages whenever you do some action on the Activity Stream about:home, about:newtab or about:welcome pages.  We try to minimize the amount and frequency of pings by batching them together.  Pings are sent in [JSON serialized format](http://www.json.org/).
 
 At Mozilla, [we take your privacy very seriously](https://www.mozilla.org/privacy/).  The Activity Stream page will never send any data that could personally identify you.  We do not transmit what you are browsing, searches you perform or any private settings.  Activity Stream does not set or send cookies, and uses [Transport Layer Security](https://en.wikipedia.org/wiki/Transport_Layer_Security) to securely transmit data to Mozilla servers.
 
 Data collected from Activity Stream is retained on Mozilla secured servers for a period of 30 days before being rolled up into an anonymous aggregated format.  After this period the raw data is deleted permanently.  Mozilla **never shares data with any third party**.
 
 The following is a detailed overview of the different kinds of data we collect in the Activity Stream. See [data_dictionary.md](data_dictionary.md) for more details for each field.
@@ -164,16 +164,53 @@ A user event ping includes some basic me
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
   "session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
   "addon_version": "20180710100040",
   "locale": "en-US",
   "user_prefs": 7
 }
 ```
 
+#### Showing privacy information
+
+```js
+{
+  "event": "SHOW_PRIVACY_INFO",
+  "source": "TOP_SITES",
+  "action_position": 2,
+
+  // Basic metadata
+  "action": "activity_stream_event",
+  "page": ["about:newtab" | "about:home" | "about:welcome" | "unknown"],
+  "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
+  "session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
+  "addon_version": "20180710100040",
+  "locale": "en-US",
+  "user_prefs": 7
+}
+```
+
+#### Clicking on privacy information link
+
+```js
+{
+  "event": "CLICK_PRIVACY_INFO",
+  "source": "DS_PRIVACY_MODAL",
+
+  // Basic metadata
+  "action": "activity_stream_event",
+  "page": ["about:newtab" | "about:home" | "about:welcome" | "unknown"],
+  "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
+  "session_id": "005deed0-e3e4-4c02-a041-17405fd703f6",
+  "addon_version": "20180710100040",
+  "locale": "en-US",
+  "user_prefs": 7
+}
+```
+
 #### Deleting a search shortcut
 ```js
 {
   "event": "SEARCH_EDIT_DELETE",
   "source": "TOP_SITES",
   "action_position": 2,
   "value": {
     "search_vendor": "google"
@@ -404,17 +441,17 @@ A user event ping includes some basic me
 ```
 
 ### Onboarding user events on about:welcome
 
 #### Form Submit Events
 
 ```js
 {
-  "event": ["SUBMIT_EMAIL" | "SKIPPED_SIGNIN"],
+  "event": ["SUBMIT_EMAIL" | "SUBMIT_SIGNIN" | "SKIPPED_SIGNIN"],
   "value": {
     "has_flow_params": false,
   }
 
   // Basic metadata
   "action": "activity_stream_event",
   "page": "about:welcome",
   "client_id": "26288a14-5cc4-d14f-ae0a-bb01ef45be9c",
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -1888,16 +1888,30 @@ class _ASRouter {
         );
         break;
       case ra.OPEN_PREFERENCES_PAGE:
         target.browser.ownerGlobal.openPreferences(action.data.category);
         break;
       case ra.OPEN_APPLICATIONS_MENU:
         UITour.showMenu(target.browser.ownerGlobal, action.data.args);
         break;
+      case ra.HIGHLIGHT_FEATURE:
+        const highlight = await UITour.getTarget(
+          target.browser.ownerGlobal,
+          action.data.args
+        );
+        if (highlight) {
+          await UITour.showHighlight(
+            target.browser.ownerGlobal,
+            highlight,
+            "none",
+            { autohide: true }
+          );
+        }
+        break;
       case ra.INSTALL_ADDON_FROM_URL:
         this._updateOnboardingState();
         await MessageLoaderUtils.installAddonFromURL(
           target.browser,
           action.data.url,
           action.data.telemetrySource
         );
         break;
--- a/browser/components/newtab/lib/ASRouterTargeting.jsm
+++ b/browser/components/newtab/lib/ASRouterTargeting.jsm
@@ -16,16 +16,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   ProfileAge: "resource://gre/modules/ProfileAge.jsm",
   ShellService: "resource:///modules/ShellService.jsm",
   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   AttributionCode: "resource:///modules/AttributionCode.jsm",
   FilterExpressions:
     "resource://gre/modules/components-utils/FilterExpressions.jsm",
+  fxAccounts: "resource://gre/modules/FxAccounts.jsm",
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "cfrFeaturesUserPref",
   "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
   true
 );
@@ -455,16 +456,22 @@ const TargetingGetters = {
       cfrFeatures: cfrFeaturesUserPref,
       cfrAddons: cfrAddonsUserPref,
       snippets: snippetsUserPref,
     };
   },
   get totalBlockedCount() {
     return TrackingDBService.sumAllEvents();
   },
+  get attachedFxAOAuthClients() {
+    return this.usesFirefoxSync ? fxAccounts.listAttachedOAuthClients() : [];
+  },
+  get platformName() {
+    return AppConstants.platform;
+  },
 };
 
 this.ASRouterTargeting = {
   Environment: TargetingGetters,
 
   ERROR_TYPES: {
     MALFORMED_EXPRESSION: "MALFORMED_EXPRESSION",
     OTHER_ERROR: "OTHER_ERROR",
--- a/browser/components/newtab/lib/ASRouterTriggerListeners.jsm
+++ b/browser/components/newtab/lib/ASRouterTriggerListeners.jsm
@@ -73,16 +73,74 @@ function checkURLMatch(aLocationURI, { h
 }
 
 /**
  * A Map from trigger IDs to singleton trigger listeners. Each listener must
  * have idempotent `init` and `uninit` methods.
  */
 this.ASRouterTriggerListeners = new Map([
   [
+    "openArticleURL",
+    {
+      id: "openArticleURL",
+      _initialized: false,
+      _triggerHandler: null,
+      _hosts: new Set(),
+      _matchPatternSet: null,
+      readerModeEvent: "Reader:UpdateReaderButton",
+
+      init(triggerHandler, hosts, patterns) {
+        if (!this._initialized) {
+          this.receiveMessage = this.receiveMessage.bind(this);
+          Services.mm.addMessageListener(this.readerModeEvent, this);
+          this._triggerHandler = triggerHandler;
+          this._initialized = true;
+        }
+        if (patterns) {
+          if (this._matchPatternSet) {
+            this._matchPatternSet = new MatchPatternSet(
+              new Set([...this._matchPatternSet.patterns, ...patterns]),
+              MATCH_PATTERN_OPTIONS
+            );
+          } else {
+            this._matchPatternSet = new MatchPatternSet(
+              patterns,
+              MATCH_PATTERN_OPTIONS
+            );
+          }
+        }
+        if (hosts) {
+          hosts.forEach(h => this._hosts.add(h));
+        }
+      },
+
+      receiveMessage({ data, target }) {
+        if (data && data.isArticle) {
+          const match = checkURLMatch(target.currentURI, {
+            hosts: this._hosts,
+            matchPatternSet: this._matchPatternSet,
+          });
+          if (match) {
+            this._triggerHandler(target, { id: this.id, param: match });
+          }
+        }
+      },
+
+      uninit() {
+        if (this._initialized) {
+          Services.mm.removeMessageListener(this.readerModeEvent, this);
+          this._initialized = false;
+          this._triggerHandler = null;
+          this._hosts = new Set();
+          this._matchPatternSet = null;
+        }
+      },
+    },
+  ],
+  [
     "openBookmarkedURL",
     {
       id: "openBookmarkedURL",
       _initialized: false,
       _triggerHandler: null,
       _hosts: new Set(),
       bookmarkEvent: "bookmark-icon-updated",
 
@@ -430,24 +488,17 @@ this.ASRouterTriggerListeners = new Map(
       },
 
       uninit() {
         if (this._initialized) {
           Services.obs.removeObserver(
             this,
             "SiteProtection:ContentBlockingEvent"
           );
-
-          for (let win of Services.wm.getEnumerator("navigator:browser")) {
-            if (isPrivateWindow(win)) {
-              continue;
-            }
-            win.gBrowser.removeTabsProgressListener(this);
-          }
-
+          EveryWindow.unregisterCallback(this.id);
           this.onLocationChange = null;
           this._initialized = false;
         }
         this._triggerHandler = null;
         this._events = [];
         this._sessionPageLoad = 0;
       },
 
--- a/browser/components/newtab/lib/ActivityStream.jsm
+++ b/browser/components/newtab/lib/ActivityStream.jsm
@@ -463,16 +463,24 @@ const PREFS_CONFIG = new Map([
         type: "remote-settings",
         bucket: "cfr-fxa",
         frequency: { custom: [{ period: "daily", cap: 1 }] },
       }),
     },
   ],
   // See browser/app/profile/firefox.js for other ASR preferences. They must be defined there to enable roll-outs.
   [
+    "discoverystream.campaign.blocks",
+    {
+      title: "Track campaign blocks",
+      skipBroadcast: true,
+      value: "{}",
+    },
+  ],
+  [
     "discoverystream.config",
     {
       title: "Configuration for the new pocket new tab",
       getValue: ({ geo, locale }) => {
         // PLEASE NOTE:
         // hardcoded_layout in `lib/DiscoveryStreamFeed.jsm` only works for en-* and requires refactoring for non english locales
         const dsEnablementMatrix = {
           US: ["en-CA", "en-GB", "en-US"],
--- a/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
+++ b/browser/components/newtab/lib/DiscoveryStreamFeed.jsm
@@ -57,16 +57,17 @@ const PREF_ENDPOINTS = "discoverystream.
 const PREF_IMPRESSION_ID = "browser.newtabpage.activity-stream.impressionId";
 const PREF_ENABLED = "discoverystream.enabled";
 const PREF_HARDCODED_BASIC_LAYOUT = "discoverystream.hardcoded-basic-layout";
 const PREF_SPOCS_ENDPOINT = "discoverystream.spocs-endpoint";
 const PREF_TOPSTORIES = "feeds.section.topstories";
 const PREF_SPOCS_CLEAR_ENDPOINT = "discoverystream.endpointSpocsClear";
 const PREF_SHOW_SPONSORED = "showSponsored";
 const PREF_SPOC_IMPRESSIONS = "discoverystream.spoc.impressions";
+const PREF_CAMPAIGN_BLOCKS = "discoverystream.campaign.blocks";
 const PREF_REC_IMPRESSIONS = "discoverystream.rec.impressions";
 
 let defaultLayoutResp;
 let basicLayoutResp;
 
 this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
   constructor() {
     // Internal state for checking if we've intialized all our data
@@ -768,18 +769,21 @@ this.DiscoveryStreamFeed = class Discove
       }
     }
     return item;
   }
 
   filterBlocked(data) {
     const filtered = [];
     if (data && data.length) {
+      let campaigns = this.readDataPref(PREF_CAMPAIGN_BLOCKS);
       const filteredItems = data.filter(item => {
-        const blocked = NewTabUtils.blockedLinks.isBlocked({ url: item.url });
+        const blocked =
+          NewTabUtils.blockedLinks.isBlocked({ url: item.url }) ||
+          campaigns[item.campaign_id];
         if (blocked) {
           filtered.push(item);
         }
         return !blocked;
       });
       return {
         data: filteredItems,
         filtered,
@@ -833,17 +837,17 @@ this.DiscoveryStreamFeed = class Discove
 
   // Filter spocs based on frequency caps
   //
   // @param {Object} data  An object that might have a SPOCS array.
   // @returns {Object} An object with a property `data` as the result, and a property
   //                   `filterItems` as the frequency capped items.
   frequencyCapSpocs(spocs) {
     if (spocs && spocs.length) {
-      const impressions = this.readImpressionsPref(PREF_SPOC_IMPRESSIONS);
+      const impressions = this.readDataPref(PREF_SPOC_IMPRESSIONS);
       const caps = [];
       const result = spocs.filter(s => {
         const isBelow = this.isBelowFrequencyCap(impressions, s);
         if (!isBelow) {
           caps.push(s);
         }
         return isBelow;
       });
@@ -1004,17 +1008,17 @@ this.DiscoveryStreamFeed = class Discove
   // We have to rotate stories on the client so that
   // active stories are at the front of the list, followed by stories that have expired
   // impressions i.e. have been displayed for longer than recsExpireTime.
   rotate(recommendations, recsExpireTime) {
     const maxImpressionAge = Math.max(
       recsExpireTime * 1000 || DEFAULT_RECS_EXPIRE_TIME,
       DEFAULT_RECS_EXPIRE_TIME
     );
-    const impressions = this.readImpressionsPref(PREF_REC_IMPRESSIONS);
+    const impressions = this.readDataPref(PREF_REC_IMPRESSIONS);
     const expired = [];
     const active = [];
     for (const item of recommendations) {
       if (
         impressions[item.id] &&
         Date.now() - impressions[item.id] >= maxImpressionAge
       ) {
         expired.push(item);
@@ -1117,34 +1121,35 @@ this.DiscoveryStreamFeed = class Discove
     await this.refreshAll({ updateOpenTabs: true, isStartup: true });
     Services.obs.addObserver(this, "idle-daily");
     this.loaded = true;
     this.totalRequestTime = Math.round(perfService.absNow() - start);
     this.reportRequestTime();
   }
 
   async reset() {
-    this.resetImpressionPrefs();
+    this.resetDataPrefs();
     await this.resetCache();
     if (this.loaded) {
       Services.obs.removeObserver(this, "idle-daily");
     }
     this.resetState();
   }
 
   async resetCache() {
     await this.cache.set("layout", {});
     await this.cache.set("feeds", {});
     await this.cache.set("spocs", {});
     await this.cache.set("affinities", {});
   }
 
-  resetImpressionPrefs() {
-    this.writeImpressionsPref(PREF_SPOC_IMPRESSIONS, {});
-    this.writeImpressionsPref(PREF_REC_IMPRESSIONS, {});
+  resetDataPrefs() {
+    this.writeDataPref(PREF_SPOC_IMPRESSIONS, {});
+    this.writeDataPref(PREF_REC_IMPRESSIONS, {});
+    this.writeDataPref(PREF_CAMPAIGN_BLOCKS, {});
   }
 
   resetState() {
     // Reset reducer
     this.store.dispatch(
       ac.BroadcastToContent({ type: at.DISCOVERY_STREAM_LAYOUT_RESET })
     );
     this.loaded = false;
@@ -1159,30 +1164,38 @@ this.DiscoveryStreamFeed = class Discove
     await this.reset();
     if (this.config.enabled) {
       // Load data from all endpoints
       await this.enable();
     }
   }
 
   recordCampaignImpression(campaignId) {
-    let impressions = this.readImpressionsPref(PREF_SPOC_IMPRESSIONS);
+    let impressions = this.readDataPref(PREF_SPOC_IMPRESSIONS);
 
     const timeStamps = impressions[campaignId] || [];
     timeStamps.push(Date.now());
     impressions = { ...impressions, [campaignId]: timeStamps };
 
-    this.writeImpressionsPref(PREF_SPOC_IMPRESSIONS, impressions);
+    this.writeDataPref(PREF_SPOC_IMPRESSIONS, impressions);
   }
 
   recordTopRecImpressions(recId) {
-    let impressions = this.readImpressionsPref(PREF_REC_IMPRESSIONS);
+    let impressions = this.readDataPref(PREF_REC_IMPRESSIONS);
     if (!impressions[recId]) {
       impressions = { ...impressions, [recId]: Date.now() };
-      this.writeImpressionsPref(PREF_REC_IMPRESSIONS, impressions);
+      this.writeDataPref(PREF_REC_IMPRESSIONS, impressions);
+    }
+  }
+
+  recordBlockCampaignId(campaignId) {
+    const campaigns = this.readDataPref(PREF_CAMPAIGN_BLOCKS);
+    if (!campaigns[campaignId]) {
+      campaigns[campaignId] = 1;
+      this.writeDataPref(PREF_CAMPAIGN_BLOCKS, campaigns);
     }
   }
 
   cleanUpCampaignImpressionPref(data) {
     let campaignIds = [];
     this.placementsForEach(placement => {
       const newSpocs = data[placement.name];
       if (!newSpocs) {
@@ -1209,38 +1222,38 @@ this.DiscoveryStreamFeed = class Discove
         return accumulator.concat(recommendations.map(i => `${i.id}`));
       }, []);
     this.cleanUpImpressionPref(
       id => !activeStories.includes(id),
       PREF_REC_IMPRESSIONS
     );
   }
 
-  writeImpressionsPref(pref, impressions) {
+  writeDataPref(pref, impressions) {
     this.store.dispatch(ac.SetPref(pref, JSON.stringify(impressions)));
   }
 
-  readImpressionsPref(pref) {
+  readDataPref(pref) {
     const prefVal = this.store.getState().Prefs.values[pref];
     return prefVal ? JSON.parse(prefVal) : {};
   }
 
   cleanUpImpressionPref(isExpired, pref) {
-    const impressions = this.readImpressionsPref(pref);
+    const impressions = this.readDataPref(pref);
     let changed = false;
 
     Object.keys(impressions).forEach(id => {
       if (isExpired(id)) {
         changed = true;
         delete impressions[id];
       }
     });
 
     if (changed) {
-      this.writeImpressionsPref(pref, impressions);
+      this.writeDataPref(pref, impressions);
     }
   }
 
   async onAction(action) {
     switch (action.type) {
       case at.INIT:
         // During the initialization of Firefox:
         // 1. Set-up listeners and initialize the redux state for config;
@@ -1326,31 +1339,36 @@ this.DiscoveryStreamFeed = class Discove
                   spocs: spocsState.data,
                 },
               })
             );
             this._sendSpocsFill({ frequency_cap: frequencyCapped }, false);
           }
         }
         break;
+      // This is fired from the browser, it has no concept of spocs, campaign or pocket.
+      // We match the blocked url with our available spoc urls to see if there is a match.
+      // I suspect we *could* instead do this in BLOCK_URL but I'm not sure.
       case at.PLACES_LINK_BLOCKED:
         if (this.showSpocs) {
           const spocsState = this.store.getState().DiscoveryStream.spocs;
           let spocsList = [];
           this.placementsForEach(placement => {
             const spocs = spocsState.data[placement.name];
             if (spocs && spocs.length) {
               spocsList = [...spocsList, ...spocs];
             }
           });
           const filtered = spocsList.filter(s => s.url === action.data.url);
           if (filtered.length) {
             this._sendSpocsFill({ blocked_by_user: filtered }, false);
 
             // If we're blocking a spoc, we want a slightly different treatment for open tabs.
+            // AlsoToPreloaded updates the source data and preloaded tabs with a new spoc.
+            // BroadcastToContent updates open tabs with a non spoc instead of a new spoc.
             this.store.dispatch(
               ac.AlsoToPreloaded({
                 type: at.DISCOVERY_STREAM_LINK_BLOCKED,
                 data: action.data,
               })
             );
             this.store.dispatch(
               ac.BroadcastToContent({
@@ -1367,16 +1385,26 @@ this.DiscoveryStreamFeed = class Discove
             data: action.data,
           })
         );
         break;
       case at.UNINIT:
         // When this feed is shutting down:
         this.uninitPrefs();
         break;
+      case at.BLOCK_URL: {
+        // If we block a story that also has a campaign_id
+        // we want to record that as blocked too.
+        // This is because a single campaign might have slightly different urls.
+        const { campaign_id } = action.data;
+        if (campaign_id) {
+          this.recordBlockCampaignId(campaign_id);
+        }
+        break;
+      }
       case at.PREF_CHANGED:
         switch (action.data.name) {
           case PREF_CONFIG:
           case PREF_ENABLED:
           case PREF_HARDCODED_BASIC_LAYOUT:
           case PREF_SPOCS_ENDPOINT:
             // Clear the cached config and broadcast the newly computed value
             this._prefCache.config = null;
--- a/browser/components/newtab/lib/OnboardingMessageProvider.jsm
+++ b/browser/components/newtab/lib/OnboardingMessageProvider.jsm
@@ -49,16 +49,30 @@ const TRAILHEAD_MODAL_VARIANT_CONTENT = 
     title: { string_id: "onboarding-welcome-form-header" },
     text: { string_id: "onboarding-join-form-body" },
     email: { string_id: "onboarding-join-form-email" },
     button: { string_id: "onboarding-join-form-continue" },
   },
   skipButton: { string_id: "onboarding-start-browsing-button-label" },
 };
 
+const TRAILHEAD_FULL_PAGE_CONTENT = {
+  title: { string_id: "onboarding-welcome-body" },
+  learn: {
+    text: { string_id: "onboarding-welcome-learn-more" },
+    url: "https://www.mozilla.org/firefox/accounts/",
+  },
+  form: {
+    title: { string_id: "onboarding-welcome-form-header" },
+    text: { string_id: "onboarding-join-form-body" },
+    email: { string_id: "onboarding-fullpage-form-email" },
+    button: { string_id: "onboarding-join-form-continue" },
+  },
+};
+
 const JOIN_CONTENT = {
   className: "joinCohort",
   title: { string_id: "onboarding-welcome-body" },
   benefits: ["products", "knowledge", "privacy"].map(id => ({
     id,
     title: { string_id: `onboarding-benefit-${id}-title` },
     text: { string_id: `onboarding-benefit-${id}-text` },
   })),
@@ -154,16 +168,37 @@ const ONBOARDING_MESSAGES = () => [
     utm_term: "trailhead-modal_variant_c",
     ...TRAILHEAD_ONBOARDING_TEMPLATE,
     content: {
       ...TRAILHEAD_MODAL_VARIANT_CONTENT,
       title: { string_id: "onboarding-welcome-modal-privacy-body" },
     },
   },
   {
+    id: "FULL_PAGE_1",
+    targeting: "trailheadInterrupt == 'full_page_d'",
+    utm_term: "trailhead-full_page_d",
+    ...TRAILHEAD_ONBOARDING_TEMPLATE,
+    content: {
+      ...TRAILHEAD_FULL_PAGE_CONTENT,
+    },
+    template: "full_page_interrupt",
+  },
+  {
+    id: "FULL_PAGE_2",
+    targeting: "trailheadInterrupt == 'full_page_e'",
+    utm_term: "trailhead-full_page_e",
+    ...TRAILHEAD_ONBOARDING_TEMPLATE,
+    content: {
+      className: "fullPageCardsAtTop",
+      ...TRAILHEAD_FULL_PAGE_CONTENT,
+    },
+    template: "full_page_interrupt",
+  },
+  {
     id: "EXTENDED_TRIPLETS_1",
     template: "extended_triplets",
     campaign: "firstrun_triplets",
     targeting:
       "trailheadTriplet && ((currentDate|date - profileAgeCreated) / 86400000) < 7",
     includeBundle: {
       length: 3,
       template: "onboarding",
@@ -228,17 +263,17 @@ const ONBOARDING_MESSAGES = () => [
   },
   {
     id: "TRAILHEAD_CARD_3",
     template: "onboarding",
     bundled: 3,
     order: 2,
     content: {
       title: { string_id: "onboarding-firefox-monitor-title" },
-      text: { string_id: "onboarding-firefox-monitor-text" },
+      text: { string_id: "onboarding-firefox-monitor-text2" },
       icon: "ffmonitor",
       primary_button: {
         label: { string_id: "onboarding-firefox-monitor-button" },
         action: {
           type: "OPEN_URL",
           data: { args: "https://monitor.firefox.com/", where: "tabshifted" },
         },
       },
@@ -456,16 +491,17 @@ const ONBOARDING_MESSAGES = () => [
   },
   {
     id: `WHATS_NEW_BADGE_${FIREFOX_VERSION}`,
     template: "toolbar_badge",
     content: {
       delay: 5 * ONE_MINUTE,
       target: "whats-new-menu-button",
       action: { id: "show-whatsnew-button" },
+      badgeDescription: { string_id: "cfr-badge-reader-label-newfeature" },
     },
     priority: 1,
     trigger: { id: "toolbarBadgeUpdate" },
     frequency: {
       // Makes it so that we track impressions for this message while at the
       // same time it can have unlimited impressions
       lifetime: Infinity,
     },
--- a/browser/components/newtab/lib/PanelTestProvider.jsm
+++ b/browser/components/newtab/lib/PanelTestProvider.jsm
@@ -235,16 +235,120 @@ const MESSAGES = () => [
       },
     },
     targeting: "true",
     trigger: {
       id: "openURL",
       patterns: ["*://*/*.pdf"],
     },
   },
+  {
+    id: "SEND_TAB_CFR",
+    template: "cfr_doorhanger",
+    content: {
+      layout: "icon_and_message",
+      category: "cfrFeatures",
+      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      heading_text: { string_id: "cfr-doorhanger-send-tab-header" },
+      info_icon: {
+        label: { string_id: "cfr-doorhanger-extension-sumo-link" },
+        sumo_path: "https://example.com",
+      },
+      text: { string_id: "cfr-doorhanger-send-tab-body" },
+      icon: "chrome://branding/content/icon64.png",
+      buttons: {
+        primary: {
+          label: { string_id: "cfr-doorhanger-send-tab-ok-button" },
+          action: {
+            type: "HIGHLIGHT_FEATURE",
+            data: { args: "pageAction-sendToDevice" },
+          },
+        },
+        secondary: [
+          {
+            label: { string_id: "cfr-doorhanger-extension-cancel-button" },
+            action: { type: "CANCEL" },
+          },
+          {
+            label: {
+              string_id: "cfr-doorhanger-extension-never-show-recommendation",
+            },
+          },
+          {
+            label: {
+              string_id: "cfr-doorhanger-extension-manage-settings-button",
+            },
+            action: {
+              type: "OPEN_PREFERENCES_PAGE",
+              data: { category: "general-cfrfeatures" },
+            },
+          },
+        ],
+      },
+    },
+    targeting: "true",
+    trigger: {
+      // Match any URL that has a Reader Mode icon
+      id: "openArticleURL",
+      patterns: ["*://*/*"],
+    },
+  },
+  {
+    id: "SEND_RECIPE_TAB_CFR",
+    template: "cfr_doorhanger",
+    // Higher priority because this has the same targeting rules as
+    // SEND_TAB_CFR but is more specific
+    priority: 1,
+    content: {
+      layout: "icon_and_message",
+      category: "cfrFeatures",
+      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      heading_text: { string_id: "cfr-doorhanger-send-tab-recipe-header" },
+      info_icon: {
+        label: { string_id: "cfr-doorhanger-extension-sumo-link" },
+        sumo_path: "https://example.com",
+      },
+      text: { string_id: "cfr-doorhanger-send-tab-body" },
+      icon: "chrome://branding/content/icon64.png",
+      buttons: {
+        primary: {
+          label: { string_id: "cfr-doorhanger-send-tab-ok-button" },
+          action: {
+            type: "HIGHLIGHT_FEATURE",
+            data: { args: "pageAction-sendToDevice" },
+          },
+        },
+        secondary: [
+          {
+            label: { string_id: "cfr-doorhanger-extension-cancel-button" },
+            action: { type: "CANCEL" },
+          },
+          {
+            label: {
+              string_id: "cfr-doorhanger-extension-never-show-recommendation",
+            },
+          },
+          {
+            label: {
+              string_id: "cfr-doorhanger-extension-manage-settings-button",
+            },
+            action: {
+              type: "OPEN_PREFERENCES_PAGE",
+              data: { category: "general-cfrfeatures" },
+            },
+          },
+        ],
+      },
+    },
+    targeting: "true",
+    trigger: {
+      id: "openArticleURL",
+      params: ["www.allrecipes.com", "allrecipes.com"],
+    },
+  },
 ];
 
 const PanelTestProvider = {
   getMessages() {
     return MESSAGES().map(message => ({
       ...message,
       targeting: `providerCohorts.panel_local_testing == "SHOW_TEST"`,
     }));
--- a/browser/components/newtab/lib/PlacesFeed.jsm
+++ b/browser/components/newtab/lib/PlacesFeed.jsm
@@ -250,16 +250,19 @@ class PlacesFeed {
       this.placesObserver.handlePlacesEvent
     );
     Services.obs.removeObserver(this, LINK_BLOCKED_EVENT);
   }
 
   /**
    * observe - An observer for the LINK_BLOCKED_EVENT.
    *           Called when a link is blocked.
+   *           Links can be blocked outside of newtab,
+   *           which is why we need to listen to this
+   *           on such a generic level.
    *
    * @param  {null} subject
    * @param  {str} topic   The name of the event
    * @param  {str} value   The data associated with the event
    */
   observe(subject, topic, value) {
     if (topic === LINK_BLOCKED_EVENT) {
       this.store.dispatch(
--- a/browser/components/newtab/lib/ToolbarBadgeHub.jsm
+++ b/browser/components/newtab/lib/ToolbarBadgeHub.jsm
@@ -1,63 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-ChromeUtils.defineModuleGetter(
-  this,
-  "EveryWindow",
-  "resource:///modules/EveryWindow.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "ToolbarPanelHub",
-  "resource://activity-stream/lib/ToolbarPanelHub.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "Services",
-  "resource://gre/modules/Services.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "setTimeout",
-  "resource://gre/modules/Timer.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "clearTimeout",
-  "resource://gre/modules/Timer.jsm"
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
 );
-ChromeUtils.defineModuleGetter(
-  this,
-  "Services",
-  "resource://gre/modules/Services.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "PrivateBrowsingUtils",
-  "resource://gre/modules/PrivateBrowsingUtils.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "setInterval",
-  "resource://gre/modules/Timer.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "clearInterval",
-  "resource://gre/modules/Timer.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  this,
-  "requestIdleCallback",
-  "resource://gre/modules/Timer.jsm"
-);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  EveryWindow: "resource:///modules/EveryWindow.jsm",
+  ToolbarPanelHub: "resource://activity-stream/lib/ToolbarPanelHub.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
+});
+
+const {
+  setInterval,
+  clearInterval,
+  requestIdleCallback,
+  setTimeout,
+  clearTimeout,
+} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
 
 // Frequency at which to check for new messages
 const SYSTEM_TICK_INTERVAL = 5 * 60 * 1000;
 let notificationsByWindow = new WeakMap();
 
 class _ToolbarBadgeHub {
   constructor() {
     this.id = "toolbar-badge-hub";
@@ -146,16 +114,20 @@ class _ToolbarBadgeHub {
         this.messageRequest({
           triggerId: "toolbarBadgeUpdate",
           template: "toolbar_badge",
         });
         break;
     }
   }
 
+  maybeInsertFTL(win) {
+    win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl");
+  }
+
   executeAction({ id, data, message_id }) {
     switch (id) {
       case "show-whatsnew-button":
         ToolbarPanelHub.enableToolbarButton();
         ToolbarPanelHub.enableAppmenuButton();
         break;
       case "moments-wnp":
         const { url, expireDelta } = data;
@@ -229,30 +201,68 @@ class _ToolbarBadgeHub {
   }
 
   removeToolbarNotification(toolbarButton) {
     // Remove it from the element that displays the badge
     toolbarButton
       .querySelector(".toolbarbutton-badge")
       .classList.remove("feature-callout");
     toolbarButton.removeAttribute("badged");
+    // Remove id used for for aria-label badge description
+    const notificationDescription = toolbarButton.querySelector(
+      "#toolbarbutton-notification-description"
+    );
+    if (notificationDescription) {
+      notificationDescription.remove();
+      toolbarButton.removeAttribute("aria-labelledby");
+      toolbarButton.removeAttribute("aria-describedby");
+    }
   }
 
   addToolbarNotification(win, message) {
     const document = win.browser.ownerDocument;
     if (message.content.action) {
       this.executeAction({ ...message.content.action, message_id: message.id });
     }
     let toolbarbutton = document.getElementById(message.content.target);
     if (toolbarbutton) {
+      const badge = toolbarbutton.querySelector(".toolbarbutton-badge");
+      badge.classList.add("feature-callout");
       toolbarbutton.setAttribute("badged", true);
-      toolbarbutton
-        .querySelector(".toolbarbutton-badge")
-        .classList.add("feature-callout");
-
+      // If we have additional aria-label information for the notification
+      // we add this content to the hidden `toolbarbutton-text` node.
+      // We then use `aria-labelledby` to link this description to the button
+      // that received the notification badge.
+      if (message.content.badgeDescription) {
+        // Insert strings as soon as we know we're showing them
+        this.maybeInsertFTL(win);
+        toolbarbutton.setAttribute(
+          "aria-labelledby",
+          `toolbarbutton-notification-description ${message.content.target}`
+        );
+        // Because tooltiptext is different to the label, it gets duplicated as
+        // the description. Setting `describedby` to the same value as
+        // `labelledby` will be detected by the a11y code and the description
+        // will be removed.
+        toolbarbutton.setAttribute(
+          "aria-describedby",
+          `toolbarbutton-notification-description ${message.content.target}`
+        );
+        const descriptionEl = document.createElement("span");
+        descriptionEl.setAttribute(
+          "id",
+          "toolbarbutton-notification-description"
+        );
+        descriptionEl.setAttribute("hidden", true);
+        document.l10n.setAttributes(
+          descriptionEl,
+          message.content.badgeDescription.string_id
+        );
+        toolbarbutton.appendChild(descriptionEl);
+      }
       // `mousedown` event required because of the `onmousedown` defined on
       // the button that prevents `click` events from firing
       toolbarbutton.addEventListener("mousedown", this.removeAllNotifications);
       // `click` event required for keyboard accessibility
       toolbarbutton.addEventListener("click", this.removeAllNotifications);
       this.state = { notification: { id: message.id } };
 
       return toolbarbutton;
--- a/browser/components/newtab/locales-src/asrouter.ftl
+++ b/browser/components/newtab/locales-src/asrouter.ftl
@@ -81,16 +81,20 @@ cfr-doorhanger-bookmark-fxa-close-btn-to
 ## Protections panel
 
 cfr-protections-panel-header = Browse without being followed
 cfr-protections-panel-body = Keep your data to yourself. { -brand-short-name } protects you from many of the most common trackers that follow what you do online.
 cfr-protections-panel-link-text = Learn more
 
 ## What's New toolbar button and panel
 
+# This string is used by screen readers to offer a text based alternative for
+# the notification icon
+cfr-badge-reader-label-newfeature = New feature:
+
 cfr-whatsnew-button =
   .label = What’s New
   .tooltiptext = What’s New
 
 cfr-whatsnew-panel-header = What’s New
 
 cfr-whatsnew-release-notes-link-text = Read the release notes
 
--- a/browser/components/newtab/locales-src/onboarding.ftl
+++ b/browser/components/newtab/locales-src/onboarding.ftl
@@ -27,21 +27,32 @@ onboarding-welcome-form-header = Start H
 onboarding-join-form-header = Join { -brand-product-name }
 onboarding-join-form-body = Enter your email address to get started.
 onboarding-join-form-email =
     .placeholder = Enter email
 onboarding-join-form-email-error = Valid email required
 onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
 onboarding-join-form-continue = Continue
 
+# This message is followed by a link using onboarding-join-form-signin ("Sign In") as text.
+onboarding-join-form-signin-label = Already have an account?
+# Text for link to submit the sign in form
+onboarding-join-form-signin = Sign In
+
 onboarding-start-browsing-button-label = Start Browsing
 onboarding-cards-dismiss =
     .title = Dismiss
     .aria-label = Dismiss
 
+## Welcome full page string
+
+onboarding-fullpage-welcome-subheader = Let’s start exploring everything you can do.
+onboarding-fullpage-form-email =
+    .placeholder = Your email address…
+
 ## Firefox Sync modal dialog strings.
 
 onboarding-sync-welcome-header = Take { -brand-product-name } with You
 onboarding-sync-welcome-content = Get your bookmarks, history, passwords and other settings on all your devices.
 onboarding-sync-welcome-learn-more-link = Learn more about Firefox Accounts
 
 onboarding-sync-form-input =
     .placeholder = Email
@@ -93,17 +104,17 @@ onboarding-tracking-protection-text2 = {
 onboarding-tracking-protection-button2 = How it Works
 
 onboarding-data-sync-title = Take Your Settings with You
 # "Sync" is short for synchronize.
 onboarding-data-sync-text2 = Sync your bookmarks, passwords, and more everywhere you use { -brand-product-name }.
 onboarding-data-sync-button2 = Sign in to { -sync-brand-short-name }
 
 onboarding-firefox-monitor-title = Stay Alert to Data Breaches
-onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
+onboarding-firefox-monitor-text2 = { -monitor-brand-name } monitors if your email has appeared in a known data breach and alerts you if it appears in a new breach.
 onboarding-firefox-monitor-button = Sign up for Alerts
 
 onboarding-browse-privately-title = Browse Privately
 onboarding-browse-privately-text = Private Browsing clears your search and browsing history to keep it secret from anyone who uses your computer.
 onboarding-browse-privately-button = Open a Private Window
 
 onboarding-firefox-send-title = Keep Your Shared Files Private
 onboarding-firefox-send-text2 = Upload your files to { -send-brand-name } to share them with end-to-end encryption and a link that automatically expires.
--- a/browser/components/newtab/test/browser/browser_aboutwelcome.js
+++ b/browser/components/newtab/test/browser/browser_aboutwelcome.js
@@ -98,16 +98,41 @@ add_task(async function test_trailhead_b
       ".trailhead.joinCohort",
       "p[data-l10n-id=onboarding-benefit-sync-text]",
       "p[data-l10n-id=onboarding-benefit-monitor-text]",
       "p[data-l10n-id=onboarding-benefit-lockwise-text]",
     ]
   );
 
   await test_trailhead_branch(
+    "full_page_d-supercharge",
+    // Expected selectors:
+    [
+      ".trailhead-fullpage",
+      ".trailheadCard",
+      "p[data-l10n-id=onboarding-benefit-products-text]",
+      "button[data-l10n-id=onboarding-join-form-continue]",
+      "button[data-l10n-id=onboarding-join-form-signin]",
+    ]
+  );
+
+  await test_trailhead_branch(
+    "full_page_e-supercharge",
+    // Expected selectors:
+    [
+      ".fullPageCardsAtTop",
+      ".trailhead-fullpage",
+      ".trailheadCard",
+      "p[data-l10n-id=onboarding-benefit-products-text]",
+      "button[data-l10n-id=onboarding-join-form-continue]",
+      "button[data-l10n-id=onboarding-join-form-signin]",
+    ]
+  );
+
+  await test_trailhead_branch(
     "cards-multidevice",
     // Expected selectors:
     [
       "button[data-l10n-id=onboarding-mobile-phone-button]",
       "button[data-l10n-id=onboarding-pocket-anywhere-button]",
       "button[data-l10n-id=onboarding-send-tabs-button]",
     ],
     // Unexpected selectors:
--- a/browser/components/newtab/test/browser/browser_asrouter_targeting.js
+++ b/browser/components/newtab/test/browser/browser_asrouter_targeting.js
@@ -32,16 +32,21 @@ ChromeUtils.defineModuleGetter(
   "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
   "TelemetryEnvironment",
   "resource://gre/modules/TelemetryEnvironment.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "AppConstants",
+  "resource://gre/modules/AppConstants.jsm"
+);
 
 // ASRouterTargeting.isMatch
 add_task(async function should_do_correct_targeting() {
   is(
     await ASRouterTargeting.isMatch("FOO", { FOO: true }),
     true,
     "should return true for a matching value"
   );
@@ -440,16 +445,28 @@ add_task(async function checkdevToolsOpe
   const message = { id: "foo", targeting: "devToolsOpenedCount >= 5" };
   is(
     await ASRouterTargeting.findMatchingMessage({ messages: [message] }),
     message,
     "should select correct item by devToolsOpenedCount"
   );
 });
 
+add_task(async function check_platformName() {
+  const message = {
+    id: "foo",
+    targeting: `platformName == "${AppConstants.platform}"`,
+  };
+  is(
+    await ASRouterTargeting.findMatchingMessage({ messages: [message] }),
+    message,
+    "should select correct item by platformName"
+  );
+});
+
 AddonTestUtils.initMochitest(this);
 
 add_task(async function checkAddonsInfo() {
   const FAKE_ID = "testaddon@tests.mozilla.org";
   const FAKE_NAME = "Test Addon";
   const FAKE_VERSION = "0.5.7";
 
   const xpi = AddonTestUtils.createTempWebExtensionFile({
--- a/browser/components/newtab/test/browser/browser_asrouter_trigger_listeners.js
+++ b/browser/components/newtab/test/browser/browser_asrouter_trigger_listeners.js
@@ -15,16 +15,51 @@ ChromeUtils.defineModuleGetter(
 );
 
 async function openURLInWindow(window, url) {
   const { selectedBrowser } = window.gBrowser;
   await BrowserTestUtils.loadURI(selectedBrowser, url);
   await BrowserTestUtils.browserLoaded(selectedBrowser, false, url);
 }
 
+add_task(async function check_openArticleURL() {
+  const TEST_URL =
+    "https://example.com/browser/browser/components/newtab/test/browser/red_page.html";
+  const articleTrigger = ASRouterTriggerListeners.get("openArticleURL");
+
+  // Previously initialized by the Router
+  articleTrigger.uninit();
+
+  // Initialize the trigger with a new triggerHandler that resolves a promise
+  // with the URL match
+  const listenerTriggered = new Promise(resolve =>
+    articleTrigger.init((browser, match) => resolve(match), ["example.com"])
+  );
+
+  const win = await BrowserTestUtils.openNewBrowserWindow();
+  await openURLInWindow(win, TEST_URL);
+  // Send a message from the content page (the TEST_URL) to the parent
+  // This should trigger the `receiveMessage` cb in the articleTrigger
+  await ContentTask.spawn(win.gBrowser.selectedBrowser, null, async () => {
+    sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
+  });
+
+  await listenerTriggered.then(data =>
+    is(
+      data.param.url,
+      TEST_URL,
+      "We should match on the TEST_URL as a website article"
+    )
+  );
+
+  // Cleanup
+  articleTrigger.uninit();
+  await BrowserTestUtils.closeWindow(win);
+});
+
 add_task(async function check_openURL_listener() {
   const TEST_URL =
     "https://example.com/browser/browser/components/newtab/test/browser/red_page.html";
 
   let urlVisitCount = 0;
   const triggerHandler = () => urlVisitCount++;
   const openURLListener = ASRouterTriggerListeners.get("openURL");
 
--- a/browser/components/newtab/test/browser/browser_onboarding_rtamo.js
+++ b/browser/components/newtab/test/browser/browser_onboarding_rtamo.js
@@ -28,18 +28,33 @@ async function setRTAMOOnboarding() {
 
   // Reset trailhead so it loads the new branch.
   Services.prefs.clearUserPref("trailhead.firstrun.didSeeAboutWelcome");
   await ASRouter.setState({ trailheadInitialized: false });
   await ASRouter.setupTrailhead();
   ASRouter._updateMessageProviders();
   await ASRouter.loadMessagesFromAllProviders();
 
-  registerCleanupFunction(() => {
+  registerCleanupFunction(async () => {
+    // Separate cleanup methods between mac and windows
+    if (AppConstants.platform === "macosx") {
+      const { path } = Services.dirsvc.get("GreD", Ci.nsIFile).parent.parent;
+      const attributionSvc = Cc["@mozilla.org/mac-attribution;1"].getService(
+        Ci.nsIMacAttributionService
+      );
+      attributionSvc.setReferrerUrl(path, "", true);
+    }
+    // Clear cache call is only possible in a testing environment
+    let env = Cc["@mozilla.org/process/environment;1"].getService(
+      Ci.nsIEnvironment
+    );
+    env.set("XPCSHELL_TEST_PROFILE_DIR", "testing");
     Services.prefs.clearUserPref(BRANCH_PREF);
+    await AttributionCode.deleteFileAsync();
+    AttributionCode._clearCache();
   });
 }
 
 add_task(async function setup() {
   // Store it in order to restore to the original value
   const { getAddonInfo } = OnboardingMessageProvider;
   // Prevent fetching the real addon url and making a network request
   OnboardingMessageProvider.getAddonInfo = () => ({
--- a/browser/components/newtab/test/schemas/pings.js
+++ b/browser/components/newtab/test/schemas/pings.js
@@ -115,16 +115,19 @@ export const UserEventAction = Joi.objec
         "MENU_EXPAND",
         "MENU_MANAGE",
         "MENU_ADD_TOPSITE",
         "MENU_PRIVACY_NOTICE",
         "DELETE_FROM_POCKET",
         "ARCHIVE_FROM_POCKET",
         "SKIPPED_SIGNIN",
         "SUBMIT_EMAIL",
+        "SUBMIT_SIGNIN",
+        "SHOW_PRIVACY_INFO",
+        "CLICK_PRIVACY_INFO",
       ]).required(),
       source: Joi.valid(["TOP_SITES", "TOP_STORIES", "HIGHLIGHTS"]),
       action_position: Joi.number().integer(),
       value: Joi.object().keys({
         icon_type: Joi.valid([
           "tippytop",
           "rich_icon",
           "screenshot_with_icon",
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -2299,34 +2299,57 @@ describe("ASRouter", () => {
         target,
         data: { type: "TRIGGER", data: { trigger } },
       });
     });
   });
 
   describe("#UITour", () => {
     let showMenuStub;
+    const highlightTarget = { target: "target" };
     beforeEach(() => {
       showMenuStub = sandbox.stub();
-      globals.set("UITour", { showMenu: showMenuStub });
+      globals.set("UITour", {
+        showMenu: showMenuStub,
+        getTarget: sandbox
+          .stub()
+          .withArgs(sinon.match.object, "pageAaction-sendToDevice")
+          .resolves(highlightTarget),
+        showHighlight: sandbox.stub(),
+      });
     });
     it("should call UITour.showMenu with the correct params on OPEN_APPLICATIONS_MENU", async () => {
       const msg = fakeExecuteUserAction({
         type: "OPEN_APPLICATIONS_MENU",
         data: { args: "appMenu" },
       });
       await Router.onMessage(msg);
 
       assert.calledOnce(showMenuStub);
       assert.calledWith(
         showMenuStub,
         msg.target.browser.ownerGlobal,
         "appMenu"
       );
     });
+    it("should call UITour.showHighlight with the correct params on HIGHLIGHT_FEATURE", async () => {
+      const msg = fakeExecuteUserAction({
+        type: "HIGHLIGHT_FEATURE",
+        data: { args: "pageAction-sendToDevice" },
+      });
+      await Router.onMessage(msg);
+
+      assert.calledOnce(UITour.getTarget);
+      assert.calledOnce(UITour.showHighlight);
+      assert.calledWith(
+        UITour.showHighlight,
+        msg.target.browser.ownerGlobal,
+        highlightTarget
+      );
+    });
   });
 
   describe("valid preview endpoint", () => {
     it("should report an error if url protocol is not https", () => {
       sandbox.stub(Cu, "reportError");
 
       assert.equal(false, Router._validPreviewEndpoint("http://foo.com"));
       assert.calledTwice(Cu.reportError);
--- a/browser/components/newtab/test/unit/asrouter/ASRouterTriggerListeners.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouterTriggerListeners.test.js
@@ -7,16 +7,17 @@ describe("ASRouterTriggerListeners", () 
   let existingWindow;
   let isWindowPrivateStub;
   const triggerHandler = () => {};
   const openURLListener = ASRouterTriggerListeners.get("openURL");
   const frequentVisitsListener = ASRouterTriggerListeners.get("frequentVisits");
   const bookmarkedURLListener = ASRouterTriggerListeners.get(
     "openBookmarkedURL"
   );
+  const openArticleURLListener = ASRouterTriggerListeners.get("openArticleURL");
   const hosts = ["www.mozilla.com", "www.mozilla.org"];
 
   beforeEach(async () => {
     sandbox = sinon.createSandbox();
     globals = new GlobalOverrider();
     existingWindow = {
       gBrowser: {
         addTabsProgressListener: sandbox.stub(),
@@ -86,16 +87,85 @@ describe("ASRouterTriggerListeners", () 
         assert.calledOnce(newTriggerHandler);
         assert.calledWithExactly(newTriggerHandler, subject, {
           id: bookmarkedURLListener.id,
         });
       });
     });
   });
 
+  describe("openArticleURL", () => {
+    describe("#init", () => {
+      beforeEach(() => {
+        globals.set(
+          "MatchPatternSet",
+          sandbox.stub().callsFake(patterns => ({
+            patterns: new Set(patterns),
+            matches: url => patterns.includes(url),
+          }))
+        );
+        sandbox.stub(global.Services.mm, "addMessageListener");
+        sandbox.stub(global.Services.mm, "removeMessageListener");
+      });
+      afterEach(() => {
+        openArticleURLListener.uninit();
+      });
+      it("setup an event listener on init", () => {
+        openArticleURLListener.init(sandbox.stub(), hosts, hosts);
+
+        assert.calledOnce(global.Services.mm.addMessageListener);
+        assert.calledWithExactly(
+          global.Services.mm.addMessageListener,
+          openArticleURLListener.readerModeEvent,
+          sinon.match.object
+        );
+      });
+      it("should call triggerHandler correctly for matches [host match]", () => {
+        const stub = sandbox.stub();
+        const target = { currentURI: { host: hosts[0], spec: hosts[1] } };
+        openArticleURLListener.init(stub, hosts, hosts);
+
+        const [
+          ,
+          { receiveMessage },
+        ] = global.Services.mm.addMessageListener.firstCall.args;
+        receiveMessage({ data: { isArticle: true }, target });
+
+        assert.calledOnce(stub);
+        assert.calledWithExactly(stub, target, {
+          id: openArticleURLListener.id,
+          param: { host: hosts[0], url: hosts[1] },
+        });
+      });
+      it("should call triggerHandler correctly for matches [pattern match]", () => {
+        const stub = sandbox.stub();
+        const target = { currentURI: { host: null, spec: hosts[1] } };
+        openArticleURLListener.init(stub, hosts, hosts);
+
+        const [
+          ,
+          { receiveMessage },
+        ] = global.Services.mm.addMessageListener.firstCall.args;
+        receiveMessage({ data: { isArticle: true }, target });
+
+        assert.calledOnce(stub);
+        assert.calledWithExactly(stub, target, {
+          id: openArticleURLListener.id,
+          param: { host: null, url: hosts[1] },
+        });
+      });
+      it("should remove the message listener", () => {
+        openArticleURLListener.init(sandbox.stub(), hosts, hosts);
+        openArticleURLListener.uninit();
+
+        assert.calledOnce(global.Services.mm.removeMessageListener);
+      });
+    });
+  });
+
   describe("frequentVisits", () => {
     let _triggerHandler;
     beforeEach(() => {
       _triggerHandler = sandbox.stub();
       sandbox.useFakeTimers();
       frequentVisitsListener.init(_triggerHandler, hosts);
     });
     afterEach(() => {
--- a/browser/components/newtab/test/unit/asrouter/PanelTestProvider.test.js
+++ b/browser/components/newtab/test/unit/asrouter/PanelTestProvider.test.js
@@ -3,17 +3,17 @@ import schema from "content-src/asrouter
 import update_schema from "content-src/asrouter/templates/OnboardingMessage/UpdateAction.schema.json";
 import whats_new_schema from "content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json";
 const messages = PanelTestProvider.getMessages();
 
 describe("PanelTestProvider", () => {
   it("should have a message", () => {
     // Careful: when changing this number make sure that new messages also go
     // through schema verifications.
-    assert.lengthOf(messages, 9);
+    assert.lengthOf(messages, 11);
   });
   it("should be a valid message", () => {
     const fxaMessages = messages.filter(
       ({ template }) => template === "fxa_bookmark_panel"
     );
     for (let message of fxaMessages) {
       assert.jsonSchema(message.content, schema);
     }
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/test/unit/asrouter/templates/FullPageInterrupt.test.jsx
@@ -0,0 +1,120 @@
+import { mount } from "enzyme";
+import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
+import React from "react";
+import {
+  FullPageInterrupt,
+  FxAccounts,
+  FxCards,
+} from "content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt";
+import { FxASignupForm } from "content-src/asrouter/components/FxASignupForm/FxASignupForm";
+import { OnboardingCard } from "content-src/asrouter/templates/OnboardingMessage/OnboardingMessage";
+
+const CARDS = [
+  {
+    id: "CARD_1",
+    content: {
+      title: { string_id: "onboarding-private-browsing-title" },
+      text: { string_id: "onboarding-private-browsing-text" },
+      icon: "icon",
+      primary_button: {
+        label: { string_id: "onboarding-button-label-get-started" },
+        action: {
+          type: "OPEN_URL",
+          data: { args: "https://example.com/" },
+        },
+      },
+    },
+  },
+];
+
+const FAKE_FLOW_PARAMS = {
+  deviceId: "foo",
+  flowId: "abc1",
+  flowBeginTime: 1234,
+};
+
+describe("<FullPageInterrupt>", () => {
+  let wrapper;
+  let dummyNode;
+  let dispatch;
+  let onBlock;
+  let sandbox;
+  let onAction;
+  let sendTelemetryStub;
+
+  beforeEach(async () => {
+    sandbox = sinon.createSandbox();
+    dispatch = sandbox.stub();
+    onBlock = sandbox.stub();
+    onAction = sandbox.stub();
+    sendTelemetryStub = sandbox.stub();
+
+    dummyNode = document.createElement("body");
+    sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
+    const fakeDocument = {
+      getElementById() {
+        return dummyNode;
+      },
+    };
+
+    const message = (await OnboardingMessageProvider.getUntranslatedMessages()).find(
+      msg => msg.id === "FULL_PAGE_1"
+    );
+
+    wrapper = mount(
+      <FullPageInterrupt
+        message={message}
+        UTMTerm={message.utm_term}
+        fxaEndpoint="https://accounts.firefox.com/endpoint/"
+        dispatch={dispatch}
+        onBlock={onBlock}
+        onAction={onAction}
+        cards={CARDS}
+        document={fakeDocument}
+        sendUserActionTelemetry={sendTelemetryStub}
+        flowParams={FAKE_FLOW_PARAMS}
+      />
+    );
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it("should trigger onBlock on removeOverlay", () => {
+    wrapper.instance().removeOverlay();
+    assert.calledOnce(onBlock);
+  });
+
+  it("should render Full Page interrupt with accounts and triplet cards section", () => {
+    assert.lengthOf(wrapper.find(FxAccounts), 1);
+    assert.lengthOf(wrapper.find(FxCards), 1);
+  });
+
+  it("should render FxASignupForm inside FxAccounts", () => {
+    assert.lengthOf(wrapper.find(FxASignupForm), 1);
+  });
+
+  it("should display learn more link on full page", () => {
+    assert.ok(wrapper.find("a.fullpage-left-link").exists());
+  });
+
+  it("should add utm_* query params to card actions and send the right ping when a card button is clicked", () => {
+    wrapper
+      .find(OnboardingCard)
+      .find("button.onboardingButton")
+      .simulate("click");
+
+    assert.calledOnce(onAction);
+    const url = onAction.firstCall.args[0].data.args;
+    assert.equal(
+      url,
+      "https://example.com/?utm_source=activity-stream&utm_campaign=firstrun&utm_medium=referral&utm_term=trailhead-full_page_d"
+    );
+    assert.calledWith(sendTelemetryStub, {
+      event: "CLICK_BUTTON",
+      message_id: CARDS[0].id,
+      id: "TRAILHEAD",
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/test/unit/asrouter/templates/FxASignupForm.test.jsx
@@ -0,0 +1,121 @@
+import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
+import { FxASignupForm } from "content-src/asrouter/components/FxASignupForm/FxASignupForm";
+import { mount } from "enzyme";
+import React from "react";
+
+describe("<FxASignupForm>", () => {
+  let wrapper;
+  let dummyNode;
+  let dispatch;
+  let onClose;
+  let sandbox;
+
+  const FAKE_FLOW_PARAMS = {
+    deviceId: "foo",
+    flowId: "abc1",
+    flowBeginTime: 1234,
+  };
+
+  const FAKE_MESSAGE_CONTENT = {
+    title: { string_id: "onboarding-welcome-body" },
+    learn: {
+      text: { string_id: "onboarding-welcome-learn-more" },
+      url: "https://www.mozilla.org/firefox/accounts/",
+    },
+    form: {
+      title: { string_id: "onboarding-welcome-form-header" },
+      text: { string_id: "onboarding-join-form-body" },
+      email: { string_id: "onboarding-fullpage-form-email" },
+      button: { string_id: "onboarding-join-form-continue" },
+    },
+  };
+
+  beforeEach(async () => {
+    sandbox = sinon.sandbox.create();
+    dispatch = sandbox.stub();
+    onClose = sandbox.stub();
+
+    dummyNode = document.createElement("body");
+    sandbox.stub(dummyNode, "querySelector").returns(dummyNode);
+    const fakeDocument = {
+      getElementById() {
+        return dummyNode;
+      },
+    };
+
+    wrapper = mount(
+      <FxASignupForm
+        document={fakeDocument}
+        content={FAKE_MESSAGE_CONTENT}
+        dispatch={dispatch}
+        fxaEndpoint="https://accounts.firefox.com/endpoint/"
+        UTMTerm="test-utm-term"
+        flowParams={FAKE_FLOW_PARAMS}
+        onClose={onClose}
+      />
+    );
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  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 not display signin link by default", () => {
+    assert.notOk(
+      wrapper
+        .find("button[data-l10n-id='onboarding-join-form-signin']")
+        .exists()
+    );
+  });
+
+  it("should display signin when showSignInLink is true", () => {
+    wrapper.setProps({ showSignInLink: true });
+    let signIn = wrapper.find(
+      "button[data-l10n-id='onboarding-join-form-signin']"
+    );
+    assert.exists(signIn);
+  });
+
+  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: true },
+      })
+    );
+  });
+
+  it("should emit UserEvent SUBMIT_SIGNIN when submit with email disabled", () => {
+    let form = wrapper.find("form");
+    form.getDOMNode().elements.email.disabled = true;
+
+    form.simulate("submit");
+    assert.calledOnce(dispatch);
+    assert.isUserEventAction(dispatch.firstCall.args[0]);
+    assert.calledWith(
+      dispatch,
+      ac.UserEvent({
+        event: at.SUBMIT_SIGNIN,
+        value: { has_flow_params: true },
+      })
+    );
+  });
+});
--- a/browser/components/newtab/test/unit/asrouter/templates/Interrupt.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/Interrupt.test.jsx
@@ -1,8 +1,9 @@
+import { FullPageInterrupt } from "content-src/asrouter/templates/FullPageInterrupt/FullPageInterrupt";
 import { Interrupt } from "content-src/asrouter/templates/FirstRun/Interrupt";
 import { ReturnToAMO } from "content-src/asrouter/templates/ReturnToAMO/ReturnToAMO";
 import { Trailhead } from "content-src/asrouter/templates//Trailhead/Trailhead";
 import { shallow } from "enzyme";
 import React from "react";
 
 describe("<Interrupt>", () => {
   let wrapper;
@@ -15,16 +16,24 @@ describe("<Interrupt>", () => {
     assert.lengthOf(wrapper.find(ReturnToAMO), 1);
   });
   it("should render Trailhead when the message has a template of trailhead", () => {
     wrapper = shallow(
       <Interrupt message={{ id: "FOO", content: {}, template: "trailhead" }} />
     );
     assert.lengthOf(wrapper.find(Trailhead), 1);
   });
+  it("should render Full Page interrupt when the message has a template of full_page_interrupt", () => {
+    wrapper = shallow(
+      <Interrupt
+        message={{ id: "FOO", content: {}, template: "full_page_interrupt" }}
+      />
+    );
+    assert.lengthOf(wrapper.find(FullPageInterrupt), 1);
+  });
   it("should throw an error if another type of message is dispatched", () => {
     assert.throws(() => {
       wrapper = shallow(
         <Interrupt message={{ id: "FOO", template: "something" }} />
       );
     });
   });
 });
--- a/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
+++ b/browser/components/newtab/test/unit/asrouter/templates/Trailhead.test.jsx
@@ -1,9 +1,10 @@
 import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
+import { FxASignupForm } from "content-src/asrouter/components/FxASignupForm/FxASignupForm";
 import { mount } from "enzyme";
 import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
 import React from "react";
 import { Trailhead } from "content-src/asrouter/templates/Trailhead/Trailhead";
 
 export const CARDS = [
   {
     content: {
@@ -70,16 +71,20 @@ describe("<Trailhead>", () => {
       />
     );
   });
 
   afterEach(() => {
     sandbox.restore();
   });
 
+  it("should render FxASignupForm with signup email", () => {
+    assert.lengthOf(wrapper.find(FxASignupForm), 1);
+  });
+
   it("should emit UserEvent SKIPPED_SIGNIN and call nextScene when you click the start browsing button", () => {
     let skipButton = wrapper.find(".trailheadStart");
     assert.ok(skipButton.exists());
     skipButton.simulate("click");
 
     assert.calledOnce(onNextScene);
 
     assert.calledOnce(dispatch);
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSDismiss.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSDismiss.test.jsx
@@ -1,13 +1,13 @@
 import { DSDismiss } from "content-src/components/DiscoveryStreamComponents/DSDismiss/DSDismiss";
 import React from "react";
 import { shallow } from "enzyme";
 
-describe("<DSTextPromo>", () => {
+describe("<DSDismiss>", () => {
   const fakeSpoc = {
     url: "https://foo.com",
     guid: "1234",
   };
   let wrapper;
   let sandbox;
   let dispatchStub;
 
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSImage.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSImage.test.jsx
@@ -106,9 +106,28 @@ describe("Discovery Stream <DSImage>", (
     const img = mount(<DSImage />);
     const { observer } = img.instance();
     sandbox.stub(observer, "unobserve");
 
     img.unmount();
 
     assert.calledOnce(observer.unobserve);
   });
+  describe("DSImage with Idle Callback", () => {
+    let wrapper;
+    let windowStub = {
+      requestIdleCallback: sinon.stub().returns(1),
+      cancelIdleCallback: sinon.stub(),
+    };
+    beforeEach(() => {
+      wrapper = mount(<DSImage windowObj={windowStub} />);
+    });
+
+    it("should call requestIdleCallback on componentDidMount", () => {
+      assert.calledOnce(windowStub.requestIdleCallback);
+    });
+
+    it("should call cancelIdleCallback on componentWillUnmount", () => {
+      wrapper.instance().componentWillUnmount();
+      assert.calledOnce(windowStub.cancelIdleCallback);
+    });
+  });
 });
--- a/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx
+++ b/browser/components/newtab/test/unit/content-src/components/DiscoveryStreamComponents/DSPrivacyModal.test.jsx
@@ -1,25 +1,44 @@
 import { DSPrivacyModal } from "content-src/components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal";
 import { shallow, mount } from "enzyme";
+import { actionCreators as ac } from "common/Actions.jsm";
 import React from "react";
 
 describe("Discovery Stream <DSPrivacyModal>", () => {
   let sandbox;
+  let dispatch;
+  let wrapper;
   beforeEach(() => {
     sandbox = sinon.createSandbox();
+    dispatch = sandbox.stub();
+    wrapper = shallow(<DSPrivacyModal dispatch={dispatch} />);
+  });
+
+  afterEach(() => {
+    sandbox.restore();
   });
 
   it("should contain a privacy notice", () => {
     const modal = mount(<DSPrivacyModal />);
     const child = modal.find(".privacy-notice");
 
     assert.lengthOf(child, 1);
   });
 
   it("should call dispatch when modal is closed", () => {
-    let dispatch = sandbox.stub();
-    let wrapper = shallow(<DSPrivacyModal dispatch={dispatch} />);
-
     wrapper.instance().closeModal();
     assert.calledOnce(dispatch);
   });
+
+  it("should call dispatch with the correct events", () => {
+    wrapper.instance().onLinkClick();
+
+    assert.calledOnce(dispatch);
+    assert.calledWith(
+      dispatch,
+      ac.UserEvent({
+        event: "CLICK_PRIVACY_INFO",
+        source: "DS_PRIVACY_MODAL",
+      })
+    );
+  });
 });
--- a/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/DiscoveryStreamFeed.test.js
@@ -887,17 +887,17 @@ describe("DiscoveryStreamFeed", () => {
         settings: {
           recsExpireTime,
         },
       };
       const fakeImpressions = {
         first: Date.now() - recsExpireTime * 1000,
         third: Date.now(),
       };
-      sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
+      sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
 
       const result = feed.rotate(
         feedResponse.recommendations,
         feedResponse.settings.recsExpireTime
       );
 
       assert.equal(result[3].id, "first");
     });
@@ -1102,17 +1102,17 @@ describe("DiscoveryStreamFeed", () => {
               period: 1,
             },
           },
         },
       ];
       const fakeImpressions = {
         seen: [Date.now() - 1],
       };
-      sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
+      sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
 
       const { data: result, filtered } = feed.frequencyCapSpocs(fakeSpocs);
 
       assert.equal(result.length, 1);
       assert.equal(result[0].campaign_id, "not-seen");
       assert.deepEqual(filtered, [fakeSpocs[0]]);
     });
     it("should return simple structure and do nothing with no spocs", () => {
@@ -1219,26 +1219,39 @@ describe("DiscoveryStreamFeed", () => {
         feed: {},
         url: "https://feed.com",
       });
     });
   });
 
   describe("#recordCampaignImpression", () => {
     it("should return false if time based cap is hit", () => {
-      sandbox.stub(feed, "readImpressionsPref").returns({});
-      sandbox.stub(feed, "writeImpressionsPref").returns();
+      sandbox.stub(feed, "readDataPref").returns({});
+      sandbox.stub(feed, "writeDataPref").returns();
 
       feed.recordCampaignImpression("seen");
 
-      assert.calledWith(
-        feed.writeImpressionsPref,
-        SPOC_IMPRESSION_TRACKING_PREF,
-        { seen: [0] }
-      );
+      assert.calledWith(feed.writeDataPref, SPOC_IMPRESSION_TRACKING_PREF, {
+        seen: [0],
+      });
+    });
+  });
+
+  describe("#recordBlockCampaignId", () => {
+    it("should call writeDataPref with new campaign id added", () => {
+      sandbox.stub(feed, "readDataPref").returns({ "1234": 1 });
+      sandbox.stub(feed, "writeDataPref").returns();
+
+      feed.recordBlockCampaignId("5678");
+
+      assert.calledOnce(feed.readDataPref);
+      assert.calledWith(feed.writeDataPref, "discoverystream.campaign.blocks", {
+        "1234": 1,
+        "5678": 1,
+      });
     });
   });
 
   describe("#cleanUpCampaignImpressionPref", () => {
     it("should remove campaign-3 because it is no longer being used", async () => {
       const fakeSpocs = {
         spocs: [
           {
@@ -1262,49 +1275,45 @@ describe("DiscoveryStreamFeed", () => {
             },
           },
         ],
       };
       const fakeImpressions = {
         "campaign-2": [Date.now() - 1],
         "campaign-3": [Date.now() - 1],
       };
-      sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
-      sandbox.stub(feed, "writeImpressionsPref").returns();
+      sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
+      sandbox.stub(feed, "writeDataPref").returns();
 
       feed.cleanUpCampaignImpressionPref(fakeSpocs);
 
-      assert.calledWith(
-        feed.writeImpressionsPref,
-        SPOC_IMPRESSION_TRACKING_PREF,
-        { "campaign-2": [-1] }
-      );
+      assert.calledWith(feed.writeDataPref, SPOC_IMPRESSION_TRACKING_PREF, {
+        "campaign-2": [-1],
+      });
     });
   });
 
   describe("#recordTopRecImpressions", () => {
     it("should add a rec id to the rec impression pref", () => {
-      sandbox.stub(feed, "readImpressionsPref").returns({});
-      sandbox.stub(feed, "writeImpressionsPref");
+      sandbox.stub(feed, "readDataPref").returns({});
+      sandbox.stub(feed, "writeDataPref");
 
       feed.recordTopRecImpressions("rec");
 
-      assert.calledWith(
-        feed.writeImpressionsPref,
-        REC_IMPRESSION_TRACKING_PREF,
-        { rec: 0 }
-      );
+      assert.calledWith(feed.writeDataPref, REC_IMPRESSION_TRACKING_PREF, {
+        rec: 0,
+      });
     });
     it("should not add an impression if it already exists", () => {
-      sandbox.stub(feed, "readImpressionsPref").returns({ rec: 4 });
-      sandbox.stub(feed, "writeImpressionsPref");
+      sandbox.stub(feed, "readDataPref").returns({ rec: 4 });
+      sandbox.stub(feed, "writeDataPref");
 
       feed.recordTopRecImpressions("rec");
 
-      assert.notCalled(feed.writeImpressionsPref);
+      assert.notCalled(feed.writeDataPref);
     });
   });
 
   describe("#cleanUpTopRecImpressionPref", () => {
     it("should remove recs no longer being used", () => {
       const newFeeds = {
         "https://foo.com": {
           data: {
@@ -1331,58 +1340,57 @@ describe("DiscoveryStreamFeed", () => {
           },
         },
       };
       const fakeImpressions = {
         rec2: Date.now() - 1,
         rec3: Date.now() - 1,
         rec5: Date.now() - 1,
       };
-      sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
-      sandbox.stub(feed, "writeImpressionsPref").returns();
+      sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
+      sandbox.stub(feed, "writeDataPref").returns();
 
       feed.cleanUpTopRecImpressionPref(newFeeds);
 
-      assert.calledWith(
-        feed.writeImpressionsPref,
-        REC_IMPRESSION_TRACKING_PREF,
-        { rec2: -1, rec3: -1 }
-      );
+      assert.calledWith(feed.writeDataPref, REC_IMPRESSION_TRACKING_PREF, {
+        rec2: -1,
+        rec3: -1,
+      });
     });
   });
 
-  describe("#writeImpressionsPref", () => {
+  describe("#writeDataPref", () => {
     it("should call Services.prefs.setStringPref", () => {
       sandbox.spy(feed.store, "dispatch");
       const fakeImpressions = {
         foo: [Date.now() - 1],
         bar: [Date.now() - 1],
       };
 
-      feed.writeImpressionsPref(SPOC_IMPRESSION_TRACKING_PREF, fakeImpressions);
+      feed.writeDataPref(SPOC_IMPRESSION_TRACKING_PREF, fakeImpressions);
 
       assert.calledWithMatch(feed.store.dispatch, {
         data: {
           name: SPOC_IMPRESSION_TRACKING_PREF,
           value: JSON.stringify(fakeImpressions),
         },
         type: at.SET_PREF,
       });
     });
   });
 
-  describe("#readImpressionsPref", () => {
+  describe("#readDataPref", () => {
     it("should return what's in Services.prefs.getStringPref", () => {
       const fakeImpressions = {
         foo: [Date.now() - 1],
         bar: [Date.now() - 1],
       };
       setPref(SPOC_IMPRESSION_TRACKING_PREF, fakeImpressions);
 
-      const result = feed.readImpressionsPref(SPOC_IMPRESSION_TRACKING_PREF);
+      const result = feed.readDataPref(SPOC_IMPRESSION_TRACKING_PREF);
 
       assert.deepEqual(result, fakeImpressions);
     });
   });
 
   describe("#onAction: DISCOVERY_STREAM_IMPRESSION_STATS", () => {
     it("should call recordTopRecImpressions from DISCOVERY_STREAM_IMPRESSION_STATS", async () => {
       sandbox.stub(feed, "recordTopRecImpressions").returns();
@@ -1458,17 +1466,17 @@ describe("DiscoveryStreamFeed", () => {
           id: 1,
           reason: "frequency_cap",
           displayed: 0,
           full_recalc: 0,
         },
       ];
 
       sandbox.stub(feed, "recordCampaignImpression").returns();
-      sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
+      sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
       sandbox.spy(feed.store, "dispatch");
 
       await feed.onAction({
         type: at.DISCOVERY_STREAM_SPOC_IMPRESSION,
         data: { campaign_id: "seen" },
       });
 
       assert.deepEqual(
@@ -1479,32 +1487,32 @@ describe("DiscoveryStreamFeed", () => {
         feed.store.dispatch.thirdCall.args[0].data.spoc_fills,
         spocFillResult
       );
     });
     it("should not call dispatch to ac.AlsoToPreloaded if spocs were not changed by frequency capping", async () => {
       Object.defineProperty(feed, "showSpocs", { get: () => true });
       const fakeImpressions = {};
       sandbox.stub(feed, "recordCampaignImpression").returns();
-      sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
+      sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
       sandbox.spy(feed.store, "dispatch");
 
       await feed.onAction({
         type: at.DISCOVERY_STREAM_SPOC_IMPRESSION,
         data: { campaign_id: "seen" },
       });
 
       assert.notCalled(feed.store.dispatch);
     });
     it("should attempt feq cap on valid spocs with placements on impression", async () => {
       sandbox.restore();
       Object.defineProperty(feed, "showSpocs", { get: () => true });
       const fakeImpressions = {};
       sandbox.stub(feed, "recordCampaignImpression").returns();
-      sandbox.stub(feed, "readImpressionsPref").returns(fakeImpressions);
+      sandbox.stub(feed, "readDataPref").returns(fakeImpressions);
       sandbox.spy(feed.store, "dispatch");
       sandbox.spy(feed, "frequencyCapSpocs");
 
       const data = {
         spocs: [
           {
             id: 2,
             campaign_id: "seen-2",
@@ -1617,16 +1625,31 @@ describe("DiscoveryStreamFeed", () => {
 
       assert.equal(
         feed.store.dispatch.thirdCall.args[0].type,
         "DISCOVERY_STREAM_SPOC_BLOCKED"
       );
     });
   });
 
+  describe("#onAction: BLOCK_URL", () => {
+    it("should call recordBlockCampaignId whith BLOCK_URL", async () => {
+      sandbox.stub(feed, "recordBlockCampaignId").returns();
+
+      await feed.onAction({
+        type: at.BLOCK_URL,
+        data: {
+          campaign_id: "1234",
+        },
+      });
+
+      assert.calledWith(feed.recordBlockCampaignId, "1234");
+    });
+  });
+
   describe("#onAction: INIT", () => {
     it("should be .loaded=false before initialization", () => {
       assert.isFalse(feed.loaded);
     });
     it("should load data and set .loaded=true if config.enabled is true", async () => {
       sandbox.stub(feed.cache, "set").returns(Promise.resolve());
       setPref(CONFIG_PREF_NAME, { enabled: true });
       sandbox.stub(feed, "loadLayout").returns(Promise.resolve());
@@ -1720,17 +1743,17 @@ describe("DiscoveryStreamFeed", () => {
       setPref(CONFIG_PREF_NAME, { enabled: true });
 
       sandbox.stub(feed, "resetCache").returns(Promise.resolve());
       await feed.onAction({ type: at.DISCOVERY_STREAM_CONFIG_CHANGE });
 
       assert.calledOnce(feed.resetCache);
     });
     it("should dispatch DISCOVERY_STREAM_LAYOUT_RESET from DISCOVERY_STREAM_CONFIG_CHANGE", async () => {
-      sandbox.stub(feed, "resetImpressionPrefs");
+      sandbox.stub(feed, "resetDataPrefs");
       sandbox.stub(feed, "resetCache").resolves();
       sandbox.stub(feed, "enable").resolves();
       setPref(CONFIG_PREF_NAME, { enabled: true });
       sandbox.spy(feed.store, "dispatch");
 
       await feed.onAction({ type: at.DISCOVERY_STREAM_CONFIG_CHANGE });
 
       assert.calledWithMatch(feed.store.dispatch, {
--- a/browser/components/newtab/test/unit/lib/ToolbarBadgeHub.test.js
+++ b/browser/components/newtab/test/unit/lib/ToolbarBadgeHub.test.js
@@ -1,12 +1,12 @@
 import { _ToolbarBadgeHub } from "lib/ToolbarBadgeHub.jsm";
 import { GlobalOverrider } from "test/unit/utils";
 import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
-import { _ToolbarPanelHub } from "lib/ToolbarPanelHub.jsm";
+import { _ToolbarPanelHub, ToolbarPanelHub } from "lib/ToolbarPanelHub.jsm";
 
 describe("ToolbarBadgeHub", () => {
   let sandbox;
   let instance;
   let fakeAddImpression;
   let fakeDispatch;
   let isBrowserPrivateStub;
   let fxaMessage;
@@ -18,16 +18,17 @@ describe("ToolbarBadgeHub", () => {
   let setTimeoutStub;
   let setIntervalStub;
   let addObserverStub;
   let removeObserverStub;
   let getStringPrefStub;
   let clearUserPrefStub;
   let setStringPrefStub;
   let requestIdleCallbackStub;
+  let fakeWindow;
   beforeEach(async () => {
     globals = new GlobalOverrider();
     sandbox = sinon.createSandbox();
     instance = new _ToolbarBadgeHub();
     fakeAddImpression = sandbox.stub();
     fakeDispatch = sandbox.stub();
     isBrowserPrivateStub = sandbox.stub();
     const onboardingMsgs = await OnboardingMessageProvider.getUntranslatedMessages();
@@ -39,40 +40,44 @@ describe("ToolbarBadgeHub", () => {
       classList: {
         add: sandbox.stub(),
         remove: sandbox.stub(),
       },
       setAttribute: sandbox.stub(),
       removeAttribute: sandbox.stub(),
       querySelector: sandbox.stub(),
       addEventListener: sandbox.stub(),
+      remove: sandbox.stub(),
+      appendChild: sandbox.stub(),
     };
     // Share the same element when selecting child nodes
     fakeElement.querySelector.returns(fakeElement);
     everyWindowStub = {
       registerCallback: sandbox.stub(),
       unregisterCallback: sandbox.stub(),
     };
     clearTimeoutStub = sandbox.stub();
     setTimeoutStub = sandbox.stub();
     setIntervalStub = sandbox.stub();
-    const fakeWindow = {
+    fakeWindow = {
+      MozXULElement: { insertFTLIfNeeded: sandbox.stub() },
       ownerGlobal: {
         gBrowser: {
           selectedBrowser: "browser",
         },
       },
     };
     addObserverStub = sandbox.stub();
     removeObserverStub = sandbox.stub();
     getStringPrefStub = sandbox.stub();
     clearUserPrefStub = sandbox.stub();
     setStringPrefStub = sandbox.stub();
     requestIdleCallbackStub = sandbox.stub().callsFake(fn => fn());
     globals.set({
+      ToolbarPanelHub,
       requestIdleCallback: requestIdleCallbackStub,
       EveryWindow: everyWindowStub,
       PrivateBrowsingUtils: { isBrowserPrivate: isBrowserPrivateStub },
       setTimeout: setTimeoutStub,
       clearTimeout: clearTimeoutStub,
       setInterval: setIntervalStub,
       Services: {
         wm: {
@@ -197,18 +202,22 @@ describe("ToolbarBadgeHub", () => {
 
       assert.notCalled(instance.registerBadgeNotificationListener);
     });
   });
   describe("addToolbarNotification", () => {
     let target;
     let fakeDocument;
     beforeEach(() => {
-      fakeDocument = { getElementById: sandbox.stub().returns(fakeElement) };
-      target = { browser: { ownerDocument: fakeDocument } };
+      fakeDocument = {
+        getElementById: sandbox.stub().returns(fakeElement),
+        createElement: sandbox.stub().returns(fakeElement),
+        l10n: { setAttributes: sandbox.stub() },
+      };
+      target = { ...fakeWindow, browser: { ownerDocument: fakeDocument } };
     });
     it("shouldn't do anything if target element is not found", () => {
       fakeDocument.getElementById.returns(null);
       instance.addToolbarNotification(target, fxaMessage);
 
       assert.notCalled(fakeElement.setAttribute);
     });
     it("should target the element specified in the message", () => {
@@ -247,16 +256,51 @@ describe("ToolbarBadgeHub", () => {
       instance.addToolbarNotification(target, whatsnewMessage);
 
       assert.calledOnce(instance.executeAction);
       assert.calledWithExactly(instance.executeAction, {
         ...whatsnewMessage.content.action,
         message_id: whatsnewMessage.id,
       });
     });
+    it("should create a description element", () => {
+      sandbox.stub(instance, "executeAction");
+      instance.addToolbarNotification(target, whatsnewMessage);
+
+      assert.calledOnce(fakeDocument.createElement);
+      assert.calledWithExactly(fakeDocument.createElement, "span");
+    });
+    it("should set description id to element and to button", () => {
+      sandbox.stub(instance, "executeAction");
+      instance.addToolbarNotification(target, whatsnewMessage);
+
+      assert.calledWithExactly(
+        fakeElement.setAttribute,
+        "id",
+        "toolbarbutton-notification-description"
+      );
+      assert.calledWithExactly(
+        fakeElement.setAttribute,
+        "aria-labelledby",
+        `toolbarbutton-notification-description ${
+          whatsnewMessage.content.target
+        }`
+      );
+    });
+    it("should attach fluent id to description", () => {
+      sandbox.stub(instance, "executeAction");
+      instance.addToolbarNotification(target, whatsnewMessage);
+
+      assert.calledOnce(fakeDocument.l10n.setAttributes);
+      assert.calledWithExactly(
+        fakeDocument.l10n.setAttributes,
+        fakeElement,
+        whatsnewMessage.content.badgeDescription.string_id
+      );
+    });
   });
   describe("registerBadgeNotificationListener", () => {
     let msg_no_delay;
     beforeEach(async () => {
       await instance.init(sandbox.stub().resolves(), {
         addImpression: fakeAddImpression,
         dispatch: fakeDispatch,
       });
@@ -413,20 +457,23 @@ describe("ToolbarBadgeHub", () => {
       assert.calledOnce(instance.getExpirationDate);
       assert.calledWithExactly(instance.getExpirationDate, 10);
     });
   });
   describe("removeToolbarNotification", () => {
     it("should remove the notification", () => {
       instance.removeToolbarNotification(fakeElement);
 
-      assert.calledOnce(fakeElement.removeAttribute);
+      assert.calledThrice(fakeElement.removeAttribute);
       assert.calledWithExactly(fakeElement.removeAttribute, "badged");
+      assert.calledWithExactly(fakeElement.removeAttribute, "aria-labelledby");
+      assert.calledWithExactly(fakeElement.removeAttribute, "aria-describedby");
       assert.calledOnce(fakeElement.classList.remove);
       assert.calledWithExactly(fakeElement.classList.remove, "feature-callout");
+      assert.calledOnce(fakeElement.remove);
     });
   });
   describe("removeAllNotifications", () => {
     let blockMessageByIdStub;
     let fakeEvent;
     beforeEach(async () => {
       await instance.init(sandbox.stub().resolves(), {
         dispatch: fakeDispatch,
--- a/browser/components/newtab/test/unit/unit-entry.js
+++ b/browser/components/newtab/test/unit/unit-entry.js
@@ -204,17 +204,17 @@ const TEST_GLOBAL = {
     locale: {
       get appLocaleAsLangTag() {
         return "en-US";
       },
       negotiateLanguages() {},
     },
     urlFormatter: { formatURL: str => str, formatURLPref: str => str },
     mm: {
-      addMessageListener: (msg, cb) => cb(),
+      addMessageListener: (msg, cb) => this.receiveMessage(),
       removeMessageListener() {},
     },
     appShell: { hiddenDOMWindow: { performance: new FakePerformance() } },
     obs: {
       addObserver() {},
       removeObserver() {},
     },
     telemetry: {
--- a/browser/locales/en-US/browser/newtab/asrouter.ftl
+++ b/browser/locales/en-US/browser/newtab/asrouter.ftl
@@ -81,16 +81,20 @@ cfr-doorhanger-bookmark-fxa-close-btn-to
 ## Protections panel
 
 cfr-protections-panel-header = Browse without being followed
 cfr-protections-panel-body = Keep your data to yourself. { -brand-short-name } protects you from many of the most common trackers that follow what you do online.
 cfr-protections-panel-link-text = Learn more
 
 ## What's New toolbar button and panel
 
+# This string is used by screen readers to offer a text based alternative for
+# the notification icon
+cfr-badge-reader-label-newfeature = New feature:
+
 cfr-whatsnew-button =
   .label = What’s New
   .tooltiptext = What’s New
 
 cfr-whatsnew-panel-header = What’s New
 
 cfr-whatsnew-release-notes-link-text = Read the release notes
 
--- a/browser/locales/en-US/browser/newtab/onboarding.ftl
+++ b/browser/locales/en-US/browser/newtab/onboarding.ftl
@@ -27,21 +27,32 @@ onboarding-welcome-form-header = Start H
 onboarding-join-form-header = Join { -brand-product-name }
 onboarding-join-form-body = Enter your email address to get started.
 onboarding-join-form-email =
     .placeholder = Enter email
 onboarding-join-form-email-error = Valid email required
 onboarding-join-form-legal = By proceeding, you agree to the <a data-l10n-name="terms">Terms of Service</a> and <a data-l10n-name="privacy">Privacy Notice</a>.
 onboarding-join-form-continue = Continue
 
+# This message is followed by a link using onboarding-join-form-signin ("Sign In") as text.
+onboarding-join-form-signin-label = Already have an account?
+# Text for link to submit the sign in form
+onboarding-join-form-signin = Sign In
+
 onboarding-start-browsing-button-label = Start Browsing
 onboarding-cards-dismiss =
     .title = Dismiss
     .aria-label = Dismiss
 
+## Welcome full page string
+
+onboarding-fullpage-welcome-subheader = Let’s start exploring everything you can do.
+onboarding-fullpage-form-email =
+    .placeholder = Your email address…
+
 ## Firefox Sync modal dialog strings.
 
 onboarding-sync-welcome-header = Take { -brand-product-name } with You
 onboarding-sync-welcome-content = Get your bookmarks, history, passwords and other settings on all your devices.
 onboarding-sync-welcome-learn-more-link = Learn more about Firefox Accounts
 
 onboarding-sync-form-input =
     .placeholder = Email
@@ -93,17 +104,17 @@ onboarding-tracking-protection-text2 = {
 onboarding-tracking-protection-button2 = How it Works
 
 onboarding-data-sync-title = Take Your Settings with You
 # "Sync" is short for synchronize.
 onboarding-data-sync-text2 = Sync your bookmarks, passwords, and more everywhere you use { -brand-product-name }.
 onboarding-data-sync-button2 = Sign in to { -sync-brand-short-name }
 
 onboarding-firefox-monitor-title = Stay Alert to Data Breaches
-onboarding-firefox-monitor-text = { -monitor-brand-name } monitors if your email has appeared in a data breach and alerts you if it appears in a new breach.
+onboarding-firefox-monitor-text2 = { -monitor-brand-name } monitors if your email has appeared in a known data breach and alerts you if it appears in a new breach.
 onboarding-firefox-monitor-button = Sign up for Alerts
 
 onboarding-browse-privately-title = Browse Privately
 onboarding-browse-privately-text = Private Browsing clears your search and browsing history to keep it secret from anyone who uses your computer.
 onboarding-browse-privately-button = Open a Private Window
 
 onboarding-firefox-send-title = Keep Your Shared Files Private
 onboarding-firefox-send-text2 = Upload your files to { -send-brand-name } to share them with end-to-end encryption and a link that automatically expires.