const { CFRPageActions } = ChromeUtils.import(
  "resource://activity-stream/lib/CFRPageActions.jsm"
);
const { ASRouterTriggerListeners } = ChromeUtils.import(
  "resource://activity-stream/lib/ASRouterTriggerListeners.jsm"
);
const { ASRouter } = ChromeUtils.import(
  "resource://activity-stream/lib/ASRouter.jsm"
);
const { CFRMessageProvider } = ChromeUtils.importESModule(
  "resource://activity-stream/lib/CFRMessageProvider.sys.mjs"
);

const { TelemetryFeed } = ChromeUtils.import(
  "resource://activity-stream/lib/TelemetryFeed.jsm"
);

const createDummyRecommendation = ({
  action,
  category,
  heading_text,
  layout,
  skip_address_bar_notifier,
  show_in_private_browsing,
  template,
}) => {
  let recommendation = {
    template,
    groups: ["mochitest-group"],
    content: {
      layout: layout || "addon_recommendation",
      category,
      anchor_id: "page-action-buttons",
      skip_address_bar_notifier,
      show_in_private_browsing,
      heading_text: heading_text || "Mochitest",
      info_icon: {
        label: { attributes: { tooltiptext: "Why am I seeing this" } },
        sumo_path: "extensionrecommendations",
      },
      icon: "chrome://activity-stream/content/data/content/assets/glyph-webextension-16.svg",
      icon_dark_theme:
        "chrome://activity-stream/content/data/content/assets/glyph-webextension-16.svg",
      learn_more: "extensionrecommendations",
      addon: {
        id: "addon-id",
        title: "Addon name",
        icon: "chrome://browser/skin/addons/addon-install-downloading.svg",
        author: "Author name",
        amo_url: "https://example.com",
      },
      descriptionDetails: { steps: [] },
      text: "Mochitest",
      buttons: {
        primary: {
          label: {
            value: "OK",
            attributes: { accesskey: "O" },
          },
          action: {
            type: action.type,
            data: {},
          },
        },
        secondary: [
          {
            label: {
              value: "Cancel",
              attributes: { accesskey: "C" },
            },
            action: {
              type: "CANCEL",
            },
          },
          {
            label: {
              value: "Cancel 1",
              attributes: { accesskey: "A" },
            },
          },
          {
            label: {
              value: "Cancel 2",
              attributes: { accesskey: "B" },
            },
          },
        ],
      },
    },
  };
  recommendation.content.notification_text = new String("Mochitest"); // eslint-disable-line
  recommendation.content.notification_text.attributes = {
    tooltiptext: "Mochitest tooltip",
    "a11y-announcement": "Mochitest announcement",
  };
  return recommendation;
};

function checkCFRAddonsElements(notification) {
  Assert.ok(notification.hidden === false, "Panel should be visible");
  Assert.equal(
    notification.getAttribute("data-notification-category"),
    "addon_recommendation",
    "Panel have correct data attribute"
  );
  Assert.ok(
    notification.querySelector("#cfr-notification-footer-text-and-addon-info"),
    "Panel should have addon info container"
  );
  Assert.ok(
    notification.querySelector("#cfr-notification-footer-filled-stars"),
    "Panel should have addon rating info"
  );
  Assert.ok(
    notification.querySelector("#cfr-notification-author"),
    "Panel should have author info"
  );
}

function checkCFRTrackingProtectionMilestone(notification) {
  Assert.ok(notification.hidden === false, "Panel should be visible");
  Assert.ok(
    notification.getAttribute("data-notification-category") === "short_message",
    "Panel have correct data attribute"
  );
}

function clearNotifications() {
  for (let notification of PopupNotifications._currentNotifications) {
    notification.remove();
  }

  // Clicking the primary action also removes the notification
  Assert.equal(
    PopupNotifications._currentNotifications.length,
    0,
    "Should have removed the notification"
  );
}

function trigger_cfr_panel(
  browser,
  trigger,
  {
    action = { type: "CANCEL" },
    heading_text,
    category = "cfrAddons",
    layout,
    skip_address_bar_notifier = false,
    use_single_secondary_button = false,
    show_in_private_browsing = false,
    template = "cfr_doorhanger",
  } = {}
) {
  // a fake action type will result in the action being ignored
  const recommendation = createDummyRecommendation({
    action,
    category,
    heading_text,
    layout,
    skip_address_bar_notifier,
    show_in_private_browsing,
    template,
  });
  if (category !== "cfrAddons") {
    delete recommendation.content.addon;
  }
  if (use_single_secondary_button) {
    recommendation.content.buttons.secondary = [
      recommendation.content.buttons.secondary[0],
    ];
  }

  clearNotifications();
  return CFRPageActions.addRecommendation(
    browser,
    trigger,
    recommendation,
    // Use the real AS dispatch method to trigger real notifications
    ASRouter.dispatchCFRAction
  );
}

add_setup(async function () {
  // Store it in order to restore to the original value
  const { _fetchLatestAddonVersion } = CFRPageActions;
  // Prevent fetching the real addon url and making a network request
  CFRPageActions._fetchLatestAddonVersion = x => "http://example.com";

  registerCleanupFunction(() => {
    CFRPageActions._fetchLatestAddonVersion = _fetchLatestAddonVersion;
    clearNotifications();
    CFRPageActions.clearRecommendations();
  });
});

add_task(async function test_cfr_notification_show() {
  const sendPingStub = sinon.stub(
    TelemetryFeed.prototype,
    "sendStructuredIngestionEvent"
  );
  // addRecommendation checks that scheme starts with http and host matches
  let browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  const response = await trigger_cfr_panel(browser, "example.com");
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );

  const oldFocus = document.activeElement;
  const showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  // Open the panel
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .hidden === false,
    "Panel should be visible"
  );
  Assert.equal(
    document.activeElement,
    oldFocus,
    "Focus didn't move when panel was shown"
  );

  // Check there is a primary button and click it. It will trigger the callback.
  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .button
  );
  let hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  document
    .getElementById("contextual-feature-recommendation-notification")
    .button.click();
  await hidePanel;

  // Clicking the primary action also removes the notification
  Assert.equal(
    PopupNotifications._currentNotifications.length,
    0,
    "Should have removed the notification"
  );

  Assert.ok(sendPingStub.callCount >= 1, "Recorded some events");
  let cfrPing = sendPingStub.args.find(args => args[2] === "cfr");
  Assert.equal(cfrPing[0].source, "CFR", "Got a CFR event");
  sendPingStub.restore();
});

add_task(async function test_cfr_notification_show() {
  // addRecommendation checks that scheme starts with http and host matches
  let browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  let response = await trigger_cfr_panel(browser, "example.com", {
    heading_text: "First Message",
  });
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );
  const showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );

  // Try adding another message
  response = await trigger_cfr_panel(browser, "example.com", {
    heading_text: "Second Message",
  });
  Assert.equal(
    response,
    false,
    "Should return false if second call did not add the message"
  );

  // Open the panel
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .hidden === false,
    "Panel should be visible"
  );

  Assert.equal(
    document.getElementById("cfr-notification-header-label").value,
    "First Message",
    "The first message should be visible"
  );

  // Check there is a primary button and click it. It will trigger the callback.
  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .button
  );
  let hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  document
    .getElementById("contextual-feature-recommendation-notification")
    .button.click();
  await hidePanel;

  // Clicking the primary action also removes the notification
  Assert.equal(
    PopupNotifications._currentNotifications.length,
    0,
    "Should have removed the notification"
  );
});

add_task(async function test_cfr_notification_minimize() {
  // addRecommendation checks that scheme starts with http and host matches
  let browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  let response = await trigger_cfr_panel(browser, "example.com");
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );

  await BrowserTestUtils.waitForCondition(
    () => gURLBar.hasAttribute("cfr-recommendation-state"),
    "Wait for the notification to show up and have a state"
  );
  Assert.ok(
    gURLBar.getAttribute("cfr-recommendation-state") === "expanded",
    "CFR recomendation state is correct"
  );

  gURLBar.focus();

  await BrowserTestUtils.waitForCondition(
    () => gURLBar.getAttribute("cfr-recommendation-state") === "collapsed",
    "After urlbar focus the CFR notification should collapse"
  );

  // Open the panel and click to dismiss to ensure cleanup
  const showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  // Open the panel
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  let hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  document
    .getElementById("contextual-feature-recommendation-notification")
    .button.click();
  await hidePanel;
});

add_task(async function test_cfr_notification_minimize_2() {
  // addRecommendation checks that scheme starts with http and host matches
  let browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  let response = await trigger_cfr_panel(browser, "example.com");
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );

  await BrowserTestUtils.waitForCondition(
    () => gURLBar.hasAttribute("cfr-recommendation-state"),
    "Wait for the notification to show up and have a state"
  );
  Assert.ok(
    gURLBar.getAttribute("cfr-recommendation-state") === "expanded",
    "CFR recomendation state is correct"
  );

  // Open the panel and click to dismiss to ensure cleanup
  const showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  // Open the panel
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  let hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );

  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .secondaryButton,
    "There should be a cancel button"
  );

  // Click the Not Now button
  document
    .getElementById("contextual-feature-recommendation-notification")
    .secondaryButton.click();

  await hidePanel;

  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification"),
    "The notification should not dissapear"
  );

  await BrowserTestUtils.waitForCondition(
    () => gURLBar.getAttribute("cfr-recommendation-state") === "collapsed",
    "Clicking the secondary button should collapse the notification"
  );

  clearNotifications();
  CFRPageActions.clearRecommendations();
});

add_task(async function test_cfr_addon_install() {
  // addRecommendation checks that scheme starts with http and host matches
  const browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  const response = await trigger_cfr_panel(browser, "example.com", {
    action: { type: "INSTALL_ADDON_FROM_URL" },
  });
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );

  const showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  // Open the panel
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .hidden === false,
    "Panel should be visible"
  );
  checkCFRAddonsElements(
    document.getElementById("contextual-feature-recommendation-notification")
  );

  // Check there is a primary button and click it. It will trigger the callback.
  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .button
  );
  const hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  document
    .getElementById("contextual-feature-recommendation-notification")
    .button.click();
  await hidePanel;

  await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");

  let [notification] = PopupNotifications.panel.childNodes;
  // Trying to install the addon will trigger a progress popup or an error popup if
  // running the test multiple times in a row
  Assert.ok(
    notification.id === "addon-progress-notification" ||
      notification.id === "addon-install-failed-notification",
    "Should try to install the addon"
  );

  clearNotifications();
});

add_task(
  async function test_cfr_tracking_protection_milestone_notification_remove() {
    await SpecialPowers.pushPrefEnv({
      set: [
        ["browser.contentblocking.cfr-milestone.milestone-achieved", 1000],
        [
          "browser.newtabpage.activity-stream.asrouter.providers.cfr",
          `{"id":"cfr","enabled":true,"type":"local","localProvider":"CFRMessageProvider","updateCycleInMs":3600000}`,
        ],
      ],
    });

    // addRecommendation checks that scheme starts with http and host matches
    let browser = gBrowser.selectedBrowser;
    BrowserTestUtils.loadURIString(browser, "http://example.com/");
    await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

    const showPanel = BrowserTestUtils.waitForEvent(
      PopupNotifications.panel,
      "popupshown"
    );

    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          event: "ContentBlockingMilestone",
        },
      },
      "SiteProtection:ContentBlockingMilestone"
    );

    await showPanel;

    const notification = document.getElementById(
      "contextual-feature-recommendation-notification"
    );

    checkCFRTrackingProtectionMilestone(notification);

    Assert.ok(notification.secondaryButton);
    let hidePanel = BrowserTestUtils.waitForEvent(
      PopupNotifications.panel,
      "popuphidden"
    );

    notification.secondaryButton.click();
    await hidePanel;
    await SpecialPowers.popPrefEnv();
    clearNotifications();
  }
);

add_task(async function test_cfr_addon_and_features_show() {
  // addRecommendation checks that scheme starts with http and host matches
  let browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  // Trigger Feature CFR
  let response = await trigger_cfr_panel(browser, "example.com");
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );

  let showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  // Open the panel
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  const notification = document.getElementById(
    "contextual-feature-recommendation-notification"
  );
  checkCFRAddonsElements(notification);

  // Check there is a primary button and click it. It will trigger the callback.
  Assert.ok(notification.button);
  let hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  document
    .getElementById("contextual-feature-recommendation-notification")
    .button.click();
  await hidePanel;

  // Clicking the primary action also removes the notification
  Assert.equal(
    PopupNotifications._currentNotifications.length,
    0,
    "Should have removed the notification"
  );

  // Trigger Addon CFR
  response = await trigger_cfr_panel(browser, "example.com", {
    action: { type: "PIN_CURRENT_TAB" },
    category: "cfrAddons",
  });
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );

  showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  // Open the panel
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  Assert.ok(
    document.getElementById("contextual-feature-recommendation-notification")
      .hidden === false,
    "Panel should be visible"
  );
  checkCFRAddonsElements(
    document.getElementById("contextual-feature-recommendation-notification")
  );

  // Check there is a primary button and click it. It will trigger the callback.
  Assert.ok(notification.button);
  hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  document
    .getElementById("contextual-feature-recommendation-notification")
    .button.click();
  await hidePanel;

  // Clicking the primary action also removes the notification
  Assert.equal(
    PopupNotifications._currentNotifications.length,
    0,
    "Should have removed the notification"
  );
});

add_task(async function test_onLocationChange_cb() {
  let count = 0;
  const triggerHandler = () => ++count;
  const TEST_URL =
    "https://example.com/browser/browser/components/newtab/test/browser/blue_page.html";
  const browser = gBrowser.selectedBrowser;

  await ASRouterTriggerListeners.get("openURL").init(triggerHandler, [
    "example.com",
  ]);

  BrowserTestUtils.loadURIString(browser, "about:blank");
  await BrowserTestUtils.browserLoaded(browser, false, "about:blank");

  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  Assert.equal(count, 1, "Count navigation to example.com");

  // Anchor scroll triggers a location change event with the same document
  // https://searchfox.org/mozilla-central/rev/8848b9741fc4ee4e9bc3ae83ea0fc048da39979f/uriloader/base/nsIWebProgressListener.idl#400-403
  BrowserTestUtils.loadURIString(browser, "http://example.com/#foo");
  await BrowserTestUtils.waitForLocationChange(
    gBrowser,
    "http://example.com/#foo"
  );

  Assert.equal(count, 1, "It should ignore same page navigation");

  BrowserTestUtils.loadURIString(browser, TEST_URL);
  await BrowserTestUtils.browserLoaded(browser, false, TEST_URL);

  Assert.equal(count, 2, "We moved to a new document");

  registerCleanupFunction(() => {
    ASRouterTriggerListeners.get("openURL").uninit();
  });
});

add_task(async function test_matchPattern() {
  let count = 0;
  const triggerHandler = () => ++count;
  const frequentVisitsTrigger = ASRouterTriggerListeners.get("frequentVisits");
  await frequentVisitsTrigger.init(triggerHandler, [], ["*://*.example.com/"]);

  const browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  await BrowserTestUtils.waitForCondition(
    () => frequentVisitsTrigger._visits.get("example.com").length === 1,
    "Registered pattern matched the current location"
  );

  BrowserTestUtils.loadURIString(browser, "about:config");
  await BrowserTestUtils.browserLoaded(browser, false, "about:config");

  await BrowserTestUtils.waitForCondition(
    () => frequentVisitsTrigger._visits.get("example.com").length === 1,
    "Navigated to a new page but not a match"
  );

  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  await BrowserTestUtils.waitForCondition(
    () => frequentVisitsTrigger._visits.get("example.com").length === 1,
    "Navigated to a location that matches the pattern but within 15 mins"
  );

  BrowserTestUtils.loadURIString(browser, "http://www.example.com/");
  await BrowserTestUtils.browserLoaded(
    browser,
    false,
    "http://www.example.com/"
  );

  await BrowserTestUtils.waitForCondition(
    () => frequentVisitsTrigger._visits.get("www.example.com").length === 1,
    "www.example.com is a different host that also matches the pattern."
  );
  await BrowserTestUtils.waitForCondition(
    () => frequentVisitsTrigger._visits.get("example.com").length === 1,
    "www.example.com is a different host that also matches the pattern."
  );

  registerCleanupFunction(() => {
    ASRouterTriggerListeners.get("frequentVisits").uninit();
  });
});

add_task(async function test_providerNames() {
  const providersBranch =
    "browser.newtabpage.activity-stream.asrouter.providers.";
  const cfrProviderPrefs = Services.prefs.getChildList(providersBranch);
  for (const prefName of cfrProviderPrefs) {
    const prefValue = JSON.parse(Services.prefs.getStringPref(prefName));
    if (prefValue && prefValue.id) {
      // Snippets are disabled in tests and value is set to []
      Assert.equal(
        prefValue.id,
        prefName.slice(providersBranch.length),
        "Provider id and pref name do not match"
      );
    }
  }
});

add_task(async function test_cfr_notification_keyboard() {
  // addRecommendation checks that scheme starts with http and host matches
  const browser = gBrowser.selectedBrowser;
  BrowserTestUtils.loadURIString(browser, "http://example.com/");
  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");

  const response = await trigger_cfr_panel(browser, "example.com");
  Assert.ok(
    response,
    "Should return true if addRecommendation checks were successful"
  );

  // Open the panel with the keyboard.
  // Toolbar buttons aren't always focusable; toolbar keyboard navigation
  // makes them focusable on demand. Therefore, we must force focus.
  const button = document.getElementById("contextual-feature-recommendation");
  button.setAttribute("tabindex", "-1");
  button.focus();
  button.removeAttribute("tabindex");

  let focused = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "focus",
    true
  );
  EventUtils.synthesizeKey(" ");
  await focused;
  Assert.ok(true, "Focus inside panel after button pressed");

  let hidden = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  EventUtils.synthesizeKey("KEY_Escape");
  await hidden;
  Assert.ok(true, "Panel hidden after Escape pressed");

  const showPanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  // Need to dismiss the notification to clear the RecommendationMap
  document.getElementById("contextual-feature-recommendation").click();
  await showPanel;

  const hidePanel = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );
  document
    .getElementById("contextual-feature-recommendation-notification")
    .button.click();
  await hidePanel;
});

add_task(function test_updateCycleForProviders() {
  Services.prefs
    .getChildList("browser.newtabpage.activity-stream.asrouter.providers.")
    .forEach(provider => {
      const prefValue = JSON.parse(Services.prefs.getStringPref(provider, ""));
      if (prefValue && prefValue.type === "remote-settings") {
        Assert.ok(prefValue.updateCycleInMs);
      }
    });
});

add_task(async function test_heartbeat_tactic_2() {
  clearNotifications();
  registerCleanupFunction(() => {
    // Remove the tab opened by clicking the heartbeat message
    gBrowser.removeCurrentTab();
    clearNotifications();
  });

  const msg = (await CFRMessageProvider.getMessages()).find(
    m => m.id === "HEARTBEAT_TACTIC_2"
  );
  const shown = await CFRPageActions.addRecommendation(
    gBrowser.selectedBrowser,
    null,
    {
      ...msg,
      id: `HEARTBEAT_MOCHITEST_${Date.now()}`,
      groups: ["mochitest-group"],
      targeting: true,
    },
    // Use the real AS dispatch method to trigger real notifications
    ASRouter.dispatchCFRAction
  );

  Assert.ok(shown, "Heartbeat CFR added");

  // Wait for visibility change
  BrowserTestUtils.waitForCondition(
    () => document.getElementById("contextual-feature-recommendation"),
    "Heartbeat button exists"
  );

  let newTabPromise = BrowserTestUtils.waitForNewTab(
    gBrowser,
    Services.urlFormatter.formatURL(msg.content.action.url),
    true
  );

  document.getElementById("contextual-feature-recommendation").click();

  await newTabPromise;
});

add_task(async function test_cfr_doorhanger_in_private_window() {
  const win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
  const sendPingStub = sinon.stub(
    TelemetryFeed.prototype,
    "sendStructuredIngestionEvent"
  );

  const tab = await BrowserTestUtils.openNewForegroundTab(
    win.gBrowser,
    "http://example.com/"
  );
  const browser = tab.linkedBrowser;

  const response1 = await trigger_cfr_panel(browser, "example.com");
  Assert.ok(
    !response1,
    "CFR should not be shown in a private window if show_in_private_browsing is false"
  );

  const response2 = await trigger_cfr_panel(browser, "example.com", {
    show_in_private_browsing: true,
  });
  Assert.ok(
    response2,
    "CFR should be shown in a private window if show_in_private_browsing is true"
  );

  const shownPromise = BrowserTestUtils.waitForEvent(
    win.PopupNotifications.panel,
    "popupshown"
  );
  win.document.getElementById("contextual-feature-recommendation").click();
  await shownPromise;

  const hiddenPromise = BrowserTestUtils.waitForEvent(
    win.PopupNotifications.panel,
    "popuphidden"
  );
  const button = win.document.getElementById(
    "contextual-feature-recommendation-notification"
  )?.button;
  Assert.ok(button, "CFR doorhanger button found");
  button.click();
  await hiddenPromise;

  Assert.greater(sendPingStub.callCount, 0, "Recorded CFR telemetry");
  const cfrPing = sendPingStub.args.find(args => args[2] === "cfr");
  Assert.equal(cfrPing[0].source, "CFR", "Got a CFR event");
  Assert.equal(
    cfrPing[0].message_id,
    "n/a",
    "Omitted message_id consistent with CFR telemetry policy"
  );
  Assert.equal(
    cfrPing[0].client_id,
    undefined,
    "Omitted client_id consistent with CFR telemetry policy"
  );

  sendPingStub.restore();
  await BrowserTestUtils.closeWindow(win);
});
