/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * Tests the buttons in the onboarding dialog for quick suggest/Firefox Suggest.
 */

ChromeUtils.defineESModuleGetters(this, {
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
});

const OTHER_DIALOG_URI = getRootDirectory(gTestPath) + "subdialog.xhtml";

// Default-branch pref values in the offline scenario.
const OFFLINE_DEFAULT_PREFS = {
  "suggest.quicksuggest.nonsponsored": true,
  "suggest.quicksuggest.sponsored": true,
  "quicksuggest.dataCollection.enabled": false,
};

let gDefaultBranch = Services.prefs.getDefaultBranch("browser.urlbar.");
let gUserBranch = Services.prefs.getBranch("browser.urlbar.");

// Allow more time for Mac and Linux machines so they don't time out in verify mode.
if (AppConstants.platform === "macosx") {
  requestLongerTimeout(4);
} else if (AppConstants.platform === "linux") {
  requestLongerTimeout(2);
}

// Whether the tab key can move the focus. On macOS with full keyboard access
// disabled (which is default), this will be false. See `canTabMoveFocus`.
let gCanTabMoveFocus;
add_setup(async function () {
  gCanTabMoveFocus = await canTabMoveFocus();
});

// When the user has already enabled the data-collection pref, the dialog should
// not appear.
add_task(async function dataCollectionAlreadyEnabled() {
  setDialogPrereqPrefs();
  UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true);

  info("Calling maybeShowOnboardingDialog");
  let showed = await QuickSuggest.maybeShowOnboardingDialog();
  Assert.ok(!showed, "The dialog was not shown");

  UrlbarPrefs.clear("quicksuggest.dataCollection.enabled");
});

// When the current tab is about:welcome, the dialog should not appear.
add_task(async function aboutWelcome() {
  setDialogPrereqPrefs();
  await BrowserTestUtils.withNewTab("about:welcome", async () => {
    info("Calling maybeShowOnboardingDialog");
    let showed = await QuickSuggest.maybeShowOnboardingDialog();
    Assert.ok(!showed, "The dialog was not shown");
  });
});

// The Escape key should dismiss the dialog without opting in. This task tests
// when Escape is pressed while the focus is inside the dialog.
add_task(async function escKey_focusInsideDialog() {
  await doDialogTest({
    callback: async () => {
      const { maybeShowPromise } = await showOnboardingDialog({
        skipIntroduction: true,
      });

      const tabCount = gBrowser.tabs.length;
      Assert.ok(
        document.activeElement.classList.contains("dialogFrame"),
        "dialogFrame is focused in the browser window"
      );

      info("Close the dialog");
      EventUtils.synthesizeKey("KEY_Escape");

      await maybeShowPromise;

      Assert.equal(
        gBrowser.currentURI.spec,
        "about:blank",
        "Nothing loaded in the current tab"
      );
      Assert.equal(gBrowser.tabs.length, tabCount, "No news tabs were opened");
    },
    onboardingDialogVersion: JSON.stringify({ version: 1 }),
    onboardingDialogChoice: "dismiss_2",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "dismiss_2",
      },
    ],
  });
});

// The Escape key should dismiss the dialog without opting in. This task tests
// when Escape is pressed while the focus is outside the dialog.
add_task(async function escKey_focusOutsideDialog() {
  await doDialogTest({
    callback: async () => {
      const { maybeShowPromise } = await showOnboardingDialog({
        skipIntroduction: true,
      });

      document.documentElement.focus();
      Assert.ok(
        !document.activeElement.classList.contains("dialogFrame"),
        "dialogFrame is not focused in the browser window"
      );

      info("Close the dialog");
      EventUtils.synthesizeKey("KEY_Escape");

      await maybeShowPromise;
    },
    onboardingDialogVersion: JSON.stringify({ version: 1 }),
    onboardingDialogChoice: "dismiss_2",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "dismiss_2",
      },
    ],
  });
});

// The Escape key should dismiss the dialog without opting in when another
// dialog is queued and shown before the onboarding. This task dismisses the
// other dialog by pressing the Escape key.
add_task(async function escKey_queued_esc() {
  await doQueuedEscKeyTest("KEY_Escape");
});

// The Escape key should dismiss the dialog without opting in when another
// dialog is queued and shown before the onboarding. This task dismisses the
// other dialog by pressing the Enter key.
add_task(async function escKey_queued_enter() {
  await doQueuedEscKeyTest("KEY_Enter");
});

async function doQueuedEscKeyTest(otherDialogKey) {
  await doDialogTest({
    callback: async () => {
      // Create promises that will resolve when each dialog is opened.
      let uris = [OTHER_DIALOG_URI, QuickSuggest.ONBOARDING_URI];
      let [otherOpenedPromise, onboardingOpenedPromise] = uris.map(uri =>
        TestUtils.topicObserved(
          "subdialog-loaded",
          contentWin => contentWin.document.documentURI == uri
        ).then(async ([contentWin]) => {
          if (contentWin.document.readyState != "complete") {
            await BrowserTestUtils.waitForEvent(contentWin, "load");
          }
        })
      );

      info("Queuing dialogs for opening");
      let otherClosedPromise = gDialogBox.open(OTHER_DIALOG_URI);
      let onboardingClosedPromise = QuickSuggest.maybeShowOnboardingDialog();

      info("Waiting for the other dialog to open");
      await otherOpenedPromise;

      info(`Pressing ${otherDialogKey} and waiting for other dialog to close`);
      EventUtils.synthesizeKey(otherDialogKey);
      await otherClosedPromise;

      info("Waiting for the onboarding dialog to open");
      await onboardingOpenedPromise;

      info("Pressing Escape and waiting for onboarding dialog to close");
      EventUtils.synthesizeKey("KEY_Escape");
      await onboardingClosedPromise;
    },
    onboardingDialogVersion: JSON.stringify({ version: 1 }),
    onboardingDialogChoice: "dismiss_1",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "dismiss_1",
      },
    ],
  });
}

// Tests `dismissed_other` by closing the dialog programmatically.
add_task(async function dismissed_other_on_introduction() {
  await doDialogTest({
    callback: async () => {
      const { maybeShowPromise } = await showOnboardingDialog();
      gDialogBox._dialog.close();
      await maybeShowPromise;
    },
    onboardingDialogVersion: JSON.stringify({ version: 1 }),
    onboardingDialogChoice: "dismiss_1",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "dismiss_1",
      },
    ],
  });
});

// The default is to wait for no browser restarts to show the onboarding dialog
// on the first restart. This tests that we can override it by configuring the
// `showOnboardingDialogOnNthRestart`
add_task(async function nimbus_override_wait_after_n_restarts() {
  UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
  UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
  UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);

  await QuickSuggestTestUtils.withExperiment({
    valueOverrides: {
      quickSuggestScenario: "online",
      // Wait for 1 browser restart
      quickSuggestShowOnboardingDialogAfterNRestarts: 1,
    },
    callback: async () => {
      let prefPromise = TestUtils.waitForPrefChange(
        "browser.urlbar.quicksuggest.showedOnboardingDialog",
        value => value === true
      ).then(() => info("Saw pref change"));

      // Simulate 2 restarts. this function is only called by BrowserGlue
      // on startup, the first restart would be where MR1 was shown then
      // we will show onboarding the 2nd restart after that.
      info("Simulating first restart");
      await QuickSuggest.maybeShowOnboardingDialog();

      info("Simulating second restart");
      const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
        null,
        QuickSuggest.ONBOARDING_URI,
        { isSubDialog: true }
      );
      const maybeShowPromise = QuickSuggest.maybeShowOnboardingDialog();
      const win = await dialogPromise;
      if (win.document.readyState != "complete") {
        await BrowserTestUtils.waitForEvent(win, "load");
      }
      // Close dialog.
      EventUtils.synthesizeKey("KEY_Escape");

      info("Waiting for maybeShowPromise and pref change");
      await Promise.all([maybeShowPromise, prefPromise]);
    },
  });
});

add_task(async function nimbus_skip_onboarding_dialog() {
  UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
  UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
  UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);

  await QuickSuggestTestUtils.withExperiment({
    valueOverrides: {
      quickSuggestScenario: "online",
      quickSuggestShouldShowOnboardingDialog: false,
    },
    callback: async () => {
      // Simulate 3 restarts.
      for (let i = 0; i < 3; i++) {
        info(`Simulating restart ${i + 1}`);
        await QuickSuggest.maybeShowOnboardingDialog();
      }
      Assert.ok(
        !Services.prefs.getBoolPref(
          "browser.urlbar.quicksuggest.showedOnboardingDialog",
          false
        ),
        "The showed onboarding dialog pref should not be set"
      );
    },
  });
});

add_task(async function nimbus_exposure_event() {
  const testData = [
    {
      experimentType: "modal",
      expectedRecorded: true,
    },
    {
      experimentType: "best-match",
      expectedRecorded: false,
    },
    {
      expectedRecorded: false,
    },
  ];

  for (const { experimentType, expectedRecorded } of testData) {
    info(`Nimbus exposure event test for type:[${experimentType}]`);
    UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
    UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
    UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);

    await QuickSuggestTestUtils.clearExposureEvent();

    await QuickSuggestTestUtils.withExperiment({
      valueOverrides: {
        quickSuggestScenario: "online",
        experimentType,
      },
      callback: async () => {
        info("Calling showOnboardingDialog");
        const { maybeShowPromise } = await showOnboardingDialog();
        EventUtils.synthesizeKey("KEY_Escape");
        await maybeShowPromise;

        info("Check the event");
        await QuickSuggestTestUtils.assertExposureEvent(expectedRecorded);
      },
    });
  }
});

const LOGO_TYPE = {
  FIREFOX: 1,
  MAGGLASS: 2,
  ANIMATION_MAGGLASS: 3,
};

const VARIATION_TEST_DATA = [
  {
    name: "A",
    introductionSection: {
      logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-1",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
      defaultFocusOrder: [
        "onboardingNext",
        "onboardingClose",
        "onboardingNext",
      ],
      actions: ["onboardingClose", "onboardingNext"],
    },
    mainSection: {
      logoType: LOGO_TYPE.MAGGLASS,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-1",
        "main-description": "firefox-suggest-onboarding-main-description-1",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-1",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-1",
      },
      visibility: {
        "#main-privacy-first": false,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
      defaultFocusOrder: [
        "onboardingNext",
        "onboardingAccept",
        "onboardingLearnMore",
        "onboardingReject",
        "onboardingSkipLink",
        "onboardingAccept",
      ],
      acceptFocusOrder: [
        "onboardingAccept",
        "onboardingLearnMore",
        "onboardingSubmit",
        "onboardingSkipLink",
        "onboardingAccept",
      ],
      rejectFocusOrder: [
        "onboardingReject",
        "onboardingSubmit",
        "onboardingSkipLink",
        "onboardingLearnMore",
        "onboardingReject",
      ],
      actions: [
        "onboardingAccept",
        "onboardingReject",
        "onboardingSkipLink",
        "onboardingLearnMore",
      ],
    },
  },
  {
    // We don't need to test the focus order and actions because the layout of
    // variation B-H is as same as A.
    name: "B",
    introductionSection: {
      logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-2",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
    },
    mainSection: {
      logoType: LOGO_TYPE.MAGGLASS,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-2",
        "main-description": "firefox-suggest-onboarding-main-description-2",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-1",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-1",
      },
      visibility: {
        "#main-privacy-first": false,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
    },
  },
  {
    name: "C",
    introductionSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-3",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
    },
    mainSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-3",
        "main-description": "firefox-suggest-onboarding-main-description-3",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-1",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-1",
      },
      visibility: {
        "#main-privacy-first": false,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
    },
  },
  {
    name: "D",
    introductionSection: {
      logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-4",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
    },
    mainSection: {
      logoType: LOGO_TYPE.MAGGLASS,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-4",
        "main-description": "firefox-suggest-onboarding-main-description-4",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-2",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-2",
      },
      visibility: {
        "#main-privacy-first": false,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
    },
  },
  {
    name: "E",
    introductionSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-5",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
    },
    mainSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-5",
        "main-description": "firefox-suggest-onboarding-main-description-5",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-2",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-2",
      },
      visibility: {
        "#main-privacy-first": false,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
    },
  },
  {
    name: "F",
    introductionSection: {
      logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-2",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-6",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
    },
    mainSection: {
      logoType: LOGO_TYPE.MAGGLASS,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-6",
        "main-description": "firefox-suggest-onboarding-main-description-6",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-2",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-2",
      },
      visibility: {
        "#main-privacy-first": false,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
    },
  },
  {
    name: "G",
    introductionSection: {
      logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-7",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
    },
    mainSection: {
      logoType: LOGO_TYPE.MAGGLASS,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-7",
        "main-description": "firefox-suggest-onboarding-main-description-7",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-2",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-2",
      },
      visibility: {
        "#main-privacy-first": true,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
    },
  },
  {
    name: "H",
    introductionSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
        "introduction-title": "firefox-suggest-onboarding-introduction-title-2",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": false,
        ".description-section": false,
        ".pager": true,
      },
    },
    mainSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-8",
        "main-description": "firefox-suggest-onboarding-main-description-8",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-1",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-1",
      },
      visibility: {
        "#main-privacy-first": false,
        ".description-section #onboardingLearnMore": false,
        ".accept #onboardingLearnMore": true,
        ".pager": true,
      },
    },
  },
  {
    name: "100-A",
    introductionSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        onboardingNext: "firefox-suggest-onboarding-introduction-next-button-3",
        "introduction-title": "firefox-suggest-onboarding-main-title-9",
      },
      visibility: {
        "#onboardingLearnMoreOnIntroduction": true,
        ".description-section": true,
        ".pager": true,
      },
      defaultFocusOrder: [
        "onboardingNext",
        "onboardingLearnMoreOnIntroduction",
        "onboardingClose",
        "onboardingNext",
      ],
      actions: [
        "onboardingClose",
        "onboardingNext",
        "onboardingLearnMoreOnIntroduction",
      ],
    },
    mainSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-9",
        "main-description": "firefox-suggest-onboarding-main-description-9",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label-2",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-3",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label-2",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-3",
      },
      visibility: {
        "#main-privacy-first": true,
        ".description-section #onboardingLearnMore": true,
        ".accept #onboardingLearnMore": false,
        ".pager": false,
      },
      defaultFocusOrder: [
        "onboardingNext",
        "onboardingLearnMore",
        "onboardingAccept",
        "onboardingReject",
        "onboardingSkipLink",
        "onboardingLearnMore",
      ],
      acceptFocusOrder: [
        "onboardingAccept",
        "onboardingSubmit",
        "onboardingSkipLink",
        "onboardingLearnMore",
        "onboardingAccept",
      ],
      rejectFocusOrder: [
        "onboardingReject",
        "onboardingSubmit",
        "onboardingSkipLink",
        "onboardingLearnMore",
        "onboardingReject",
      ],
      actions: [
        "onboardingAccept",
        "onboardingReject",
        "onboardingSkipLink",
        "onboardingLearnMore",
      ],
    },
  },
  {
    name: "100-B",
    mainSection: {
      logoType: LOGO_TYPE.FIREFOX,
      l10n: {
        "main-title": "firefox-suggest-onboarding-main-title-9",
        "main-description": "firefox-suggest-onboarding-main-description-9",
        "main-accept-option-label":
          "firefox-suggest-onboarding-main-accept-option-label-2",
        "main-accept-option-description":
          "firefox-suggest-onboarding-main-accept-option-description-3",
        "main-reject-option-label":
          "firefox-suggest-onboarding-main-reject-option-label-2",
        "main-reject-option-description":
          "firefox-suggest-onboarding-main-reject-option-description-3",
      },
      visibility: {
        "#main-privacy-first": true,
        ".description-section #onboardingLearnMore": true,
        ".accept #onboardingLearnMore": false,
        ".pager": false,
      },
      // Layout of 100-B is same as 100-A, but since there is no the introduction
      // pane, only the default focus order on the main pane is a bit diffrence.
      defaultFocusOrder: [
        "onboardingLearnMore",
        "onboardingAccept",
        "onboardingReject",
        "onboardingSkipLink",
        "onboardingLearnMore",
      ],
    },
  },
];

/**
 * This test checks for differences due to variations in logo type, l10n text,
 * element visibility, order of focus, actions, etc. The designation is on
 * VARIATION_TEST_DATA. The items that can be specified are below.
 *
 * name: Specify the variation name.
 *
 * The following items are specified for each section.
 * (introductionSection, mainSection).
 *
 * logoType:
 *   Specify the expected logo type. Please refer to LOGO_TYPE about the type.
 *
 * l10n:
 *   Specify the expected l10n id applied to elements.
 *
 * visibility:
 *   Specify the expected visibility of elements. The way to specify the element
 *   is using selector.
 *
 * defaultFocusOrder:
 *   Specify the expected focus order right after the section is appeared. The
 *   way to specify the element is using id.
 *
 * acceptFocusOrder:
 *   Specify the expected focus order after selecting accept option.
 *
 * rejectFocusOrder:
 *   Specify the expected focus order after selecting reject option.
 *
 * actions:
 *   Specify the action we want to verify such as clicking the  close button. The
 *   available actions are below.
 * - onboardingClose:
 *     Action of the close button “x” by mouse/keyboard.
 * - onboardingNext:
 *     Action of the next button that transits from the introduction section to
 *     the main section by mouse/keyboard.
 * - onboardingAccept:
 *      Action of the submit button by mouse/keyboard after selecting accept
 *      option by mouse/keyboard.
 * - onboardingReject:
 *      Action of the submit button by mouse/keyboard after selecting reject
 *      option by mouse/keyboard.
 * - onboardingSkipLink:
 *      Action of the skip link by mouse/keyboard.
 * - onboardingLearnMore:
 *      Action of the learn more link by mouse/keyboard.
 * - onboardingLearnMoreOnIntroduction:
 *      Action of the learn more link on the introduction section by
 *      mouse/keyboard.
 */
add_task(async function variation_test() {
  for (const variation of VARIATION_TEST_DATA) {
    info(`Test for variation [${variation.name}]`);

    info("Do layout test");
    await doLayoutTest(variation);

    for (const action of variation.introductionSection?.actions || []) {
      info(
        `${action} test on the introduction section for variation [${variation.name}]`
      );
      await this[action](variation);
    }

    for (const action of variation.mainSection?.actions || []) {
      info(
        `${action} test on the main section for variation [${variation.name}]`
      );
      await this[action](variation, !!variation.introductionSection);
    }
  }
});

async function doLayoutTest(variation) {
  UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
  UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
  UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);

  await QuickSuggestTestUtils.withExperiment({
    valueOverrides: {
      quickSuggestScenario: "online",
      quickSuggestOnboardingDialogVariation: variation.name,
    },
    callback: async () => {
      info("Calling showOnboardingDialog");
      const { win, maybeShowPromise } = await showOnboardingDialog();

      const introductionSection = win.document.getElementById(
        "introduction-section"
      );
      const mainSection = win.document.getElementById("main-section");

      if (variation.introductionSection) {
        info("Check the section visibility");
        Assert.ok(BrowserTestUtils.is_visible(introductionSection));
        Assert.ok(BrowserTestUtils.is_hidden(mainSection));

        info("Check the introduction section");
        await assertSection(introductionSection, variation.introductionSection);

        info("Transition to the main section");
        win.document.getElementById("onboardingNext").click();
        await BrowserTestUtils.waitForCondition(
          () =>
            BrowserTestUtils.is_hidden(introductionSection) &&
            BrowserTestUtils.is_visible(mainSection)
        );
      } else {
        info("Check the section visibility");
        Assert.ok(BrowserTestUtils.is_hidden(introductionSection));
        Assert.ok(BrowserTestUtils.is_visible(mainSection));
      }

      info("Check the main section");
      await assertSection(mainSection, variation.mainSection);

      info("Close the dialog");
      EventUtils.synthesizeKey("KEY_Escape", {}, win);
      await maybeShowPromise;
    },
  });
}

async function assertSection(sectionElement, expectedSection) {
  info("Check the logo");
  assertLogo(sectionElement, expectedSection.logoType);

  info("Check the l10n");
  assertL10N(sectionElement, expectedSection.l10n);

  info("Check the visibility");
  assertVisibility(sectionElement, expectedSection.visibility);

  if (!gCanTabMoveFocus) {
    Assert.ok(true, "Tab key can't move focus, skipping test for focus order");
    return;
  }

  if (expectedSection.defaultFocusOrder) {
    info("Check the default focus order");
    assertFocusOrder(sectionElement, expectedSection.defaultFocusOrder);
  }

  if (expectedSection.acceptFocusOrder) {
    info("Check the focus order after selecting accept option");
    sectionElement.querySelector("#onboardingAccept").focus();
    EventUtils.synthesizeKey("VK_SPACE", {}, sectionElement.ownerGlobal);
    assertFocusOrder(sectionElement, expectedSection.acceptFocusOrder);
  }

  if (expectedSection.rejectFocusOrder) {
    info("Check the focus order after selecting reject option");
    sectionElement.querySelector("#onboardingReject").focus();
    EventUtils.synthesizeKey("VK_SPACE", {}, sectionElement.ownerGlobal);
    assertFocusOrder(sectionElement, expectedSection.rejectFocusOrder);
  }
}

function assertLogo(sectionElement, expectedLogoType) {
  let expectedLogoImage;
  switch (expectedLogoType) {
    case LOGO_TYPE.FIREFOX: {
      expectedLogoImage = 'url("chrome://branding/content/about-logo.svg")';
      break;
    }
    case LOGO_TYPE.MAGGLASS: {
      expectedLogoImage =
        'url("chrome://browser/content/urlbar/quicksuggestOnboarding_magglass.svg")';
      break;
    }
    case LOGO_TYPE.ANIMATION_MAGGLASS: {
      const mediaQuery = sectionElement.ownerGlobal.matchMedia(
        "(prefers-reduced-motion: no-preference)"
      );
      expectedLogoImage = mediaQuery.matches
        ? 'url("chrome://browser/content/urlbar/quicksuggestOnboarding_magglass_animation.svg")'
        : 'url("chrome://browser/content/urlbar/quicksuggestOnboarding_magglass.svg")';
      break;
    }
    default: {
      Assert.ok(false, `Unexpected image type ${expectedLogoType}`);
      break;
    }
  }

  const logo = sectionElement.querySelector(".logo");
  Assert.ok(BrowserTestUtils.is_visible(logo));
  const logoImage =
    sectionElement.ownerGlobal.getComputedStyle(logo).backgroundImage;
  Assert.equal(logoImage, expectedLogoImage);
}

function assertL10N(sectionElement, expectedL10N) {
  for (const [id, l10n] of Object.entries(expectedL10N)) {
    const element = sectionElement.querySelector("#" + id);
    Assert.equal(element.getAttribute("data-l10n-id"), l10n);
  }
}

function assertVisibility(sectionElement, expectedVisibility) {
  for (const [selector, visibility] of Object.entries(expectedVisibility)) {
    const element = sectionElement.querySelector(selector);
    if (visibility) {
      Assert.ok(BrowserTestUtils.is_visible(element));
    } else {
      if (!element) {
        Assert.ok(true);
        return;
      }
      Assert.ok(BrowserTestUtils.is_hidden(element));
    }
  }
}

function assertFocusOrder(sectionElement, expectedFocusOrder) {
  const win = sectionElement.ownerGlobal;

  // Check initial active element.
  Assert.equal(win.document.activeElement.id, expectedFocusOrder[0]);

  for (const next of expectedFocusOrder.slice(1)) {
    EventUtils.synthesizeKey("KEY_Tab", {}, win);
    Assert.equal(win.document.activeElement.id, next);
  }
}

async function onboardingClose(variation) {
  await doActionTest({
    callback: async (win, userAction, maybeShowPromise) => {
      info("Check the status of the close button");
      const closeButton = win.document.getElementById("onboardingClose");
      Assert.ok(BrowserTestUtils.is_visible(closeButton));
      Assert.equal(closeButton.getAttribute("title"), "Close");

      info("Commit the close button");
      userAction(closeButton);

      info("Waiting for maybeShowOnboardingDialog to finish");
      await maybeShowPromise;
    },
    variation,
    onboardingDialogVersion: JSON.stringify({
      version: 1,
      variation: variation.name.toLowerCase(),
    }),
    onboardingDialogChoice: "close_1",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "close_1",
      },
    ],
  });
}

async function onboardingNext(variation) {
  await doActionTest({
    callback: async (win, userAction, maybeShowPromise) => {
      info("Check the status of the next button");
      const nextButton = win.document.getElementById("onboardingNext");
      Assert.ok(BrowserTestUtils.is_visible(nextButton));

      info("Commit the next button");
      userAction(nextButton);

      const introductionSection = win.document.getElementById(
        "introduction-section"
      );
      const mainSection = win.document.getElementById("main-section");
      await BrowserTestUtils.waitForCondition(
        () =>
          BrowserTestUtils.is_hidden(introductionSection) &&
          BrowserTestUtils.is_visible(mainSection),
        "Wait for the transition"
      );

      info("Exit");
      EventUtils.synthesizeKey("KEY_Escape", {}, win);

      info("Waiting for maybeShowOnboardingDialog to finish");
      await maybeShowPromise;
    },
    variation,
    onboardingDialogVersion: JSON.stringify({
      version: 1,
      variation: variation.name.toLowerCase(),
    }),
    onboardingDialogChoice: "dismiss_2",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "dismiss_2",
      },
    ],
  });
}

async function onboardingAccept(variation, skipIntroduction) {
  await doActionTest({
    callback: async (win, userAction, maybeShowPromise) => {
      info("Check the status of the accept option and submit button");
      const acceptOption = win.document.getElementById("onboardingAccept");
      const submitButton = win.document.getElementById("onboardingSubmit");
      Assert.ok(acceptOption);
      Assert.ok(submitButton.disabled);

      info("Select the accept option");
      userAction(acceptOption);

      info("Commit the submit button");
      Assert.ok(!submitButton.disabled);
      userAction(submitButton);

      info("Waiting for maybeShowOnboardingDialog to finish");
      await maybeShowPromise;
    },
    variation,
    skipIntroduction,
    onboardingDialogVersion: JSON.stringify({
      version: 1,
      variation: variation.name.toLowerCase(),
    }),
    onboardingDialogChoice: "accept_2",
    expectedUserBranchPrefs: {
      "quicksuggest.onboardingDialogVersion": JSON.stringify({ version: 1 }),
      "quicksuggest.dataCollection.enabled": true,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "enabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "accept_2",
      },
    ],
  });
}

async function onboardingReject(variation, skipIntroduction) {
  await doActionTest({
    callback: async (win, userAction, maybeShowPromise) => {
      info("Check the status of the reject option and submit button");
      const rejectOption = win.document.getElementById("onboardingReject");
      const submitButton = win.document.getElementById("onboardingSubmit");
      Assert.ok(rejectOption);
      Assert.ok(submitButton.disabled);

      info("Select the reject option");
      userAction(rejectOption);

      info("Commit the submit button");
      Assert.ok(!submitButton.disabled);
      userAction(submitButton);

      info("Waiting for maybeShowOnboardingDialog to finish");
      await maybeShowPromise;
    },
    variation,
    skipIntroduction,
    onboardingDialogVersion: JSON.stringify({
      version: 1,
      variation: variation.name.toLowerCase(),
    }),
    onboardingDialogChoice: "reject_2",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "reject_2",
      },
    ],
  });
}

async function onboardingSkipLink(variation, skipIntroduction) {
  await doActionTest({
    callback: async (win, userAction, maybeShowPromise) => {
      info("Check the status of the skip link");
      const skipLink = win.document.getElementById("onboardingSkipLink");
      Assert.ok(BrowserTestUtils.is_visible(skipLink));

      info("Commit the skip link");
      const tabCount = gBrowser.tabs.length;
      userAction(skipLink);

      info("Waiting for maybeShowOnboardingDialog to finish");
      await maybeShowPromise;

      info("Check the current tab status");
      Assert.equal(
        gBrowser.currentURI.spec,
        "about:blank",
        "Nothing loaded in the current tab"
      );
      Assert.equal(gBrowser.tabs.length, tabCount, "No news tabs were opened");
    },
    variation,
    skipIntroduction,
    onboardingDialogVersion: JSON.stringify({
      version: 1,
      variation: variation.name.toLowerCase(),
    }),
    onboardingDialogChoice: "not_now_2",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "not_now_2",
      },
    ],
  });
}

async function onboardingLearnMore(variation, skipIntroduction) {
  await doLearnMoreTest(
    variation,
    skipIntroduction,
    "onboardingLearnMore",
    "learn_more_2"
  );
}

async function onboardingLearnMoreOnIntroduction(variation, skipIntroduction) {
  await doLearnMoreTest(
    variation,
    skipIntroduction,
    "onboardingLearnMoreOnIntroduction",
    "learn_more_1"
  );
}

async function doLearnMoreTest(variation, skipIntroduction, target, telemetry) {
  await doActionTest({
    callback: async (win, userAction, maybeShowPromise) => {
      info("Check the status of the learn more link");
      const learnMoreLink = win.document.getElementById(target);
      Assert.ok(BrowserTestUtils.is_visible(learnMoreLink));

      info("Commit the learn more link");
      const loadPromise = BrowserTestUtils.waitForNewTab(
        gBrowser,
        QuickSuggest.HELP_URL
      ).then(tab => {
        info("Saw new tab");
        return tab;
      });
      userAction(learnMoreLink);

      info("Waiting for maybeShowOnboardingDialog to finish");
      await maybeShowPromise;

      info("Waiting for new tab");
      let tab = await loadPromise;

      info("Check the current tab status");
      Assert.equal(gBrowser.selectedTab, tab, "Current tab is the new tab");
      Assert.equal(
        gBrowser.currentURI.spec,
        QuickSuggest.HELP_URL,
        "Current tab is the support page"
      );
      BrowserTestUtils.removeTab(tab);
    },
    variation,
    skipIntroduction,
    onboardingDialogChoice: telemetry,
    onboardingDialogVersion: JSON.stringify({
      version: 1,
      variation: variation.name.toLowerCase(),
    }),
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: telemetry,
      },
    ],
  });
}

async function doActionTest({
  variation,
  skipIntroduction,
  callback,
  onboardingDialogVersion,
  onboardingDialogChoice,
  expectedUserBranchPrefs,
  telemetryEvents,
}) {
  const userClick = target => {
    info("Click on the target");
    target.click();
  };
  const userEnter = target => {
    target.focus();
    if (target.type === "radio") {
      info("Space on the target");
      EventUtils.synthesizeKey("VK_SPACE", {}, target.ownerGlobal);
    } else {
      info("Enter on the target");
      EventUtils.synthesizeKey("KEY_Enter", {}, target.ownerGlobal);
    }
  };

  for (const userAction of [userClick, userEnter]) {
    UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
    UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
    UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);

    await QuickSuggestTestUtils.withExperiment({
      valueOverrides: {
        quickSuggestScenario: "online",
        quickSuggestOnboardingDialogVariation: variation.name,
      },
      callback: async () => {
        await doDialogTest({
          callback: async () => {
            info("Calling showOnboardingDialog");
            const { win, maybeShowPromise } = await showOnboardingDialog({
              skipIntroduction,
            });

            await callback(win, userAction, maybeShowPromise);
          },
          onboardingDialogVersion,
          onboardingDialogChoice,
          expectedUserBranchPrefs,
          telemetryEvents,
        });
      },
    });
  }
}

async function doDialogTest({
  callback,
  onboardingDialogVersion,
  onboardingDialogChoice,
  telemetryEvents,
  expectedUserBranchPrefs,
}) {
  setDialogPrereqPrefs();

  // Set initial prefs on the default branch.
  let initialDefaultBranch = OFFLINE_DEFAULT_PREFS;
  let originalDefaultBranch = {};
  for (let [name, value] of Object.entries(initialDefaultBranch)) {
    originalDefaultBranch = gDefaultBranch.getBoolPref(name);
    gDefaultBranch.setBoolPref(name, value);
    gUserBranch.clearUserPref(name);
  }

  // Setting the prefs just now triggered telemetry events, so clear them
  // before calling the callback.
  Services.telemetry.clearEvents();

  // Call the callback, which should trigger the dialog and interact with it.
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await callback();
  });

  // Now check all pref values on the default and user branches.
  for (let [name, value] of Object.entries(initialDefaultBranch)) {
    Assert.equal(
      gDefaultBranch.getBoolPref(name),
      value,
      "Default-branch value for pref did not change after modal: " + name
    );

    let effectiveValue;
    if (name in expectedUserBranchPrefs) {
      effectiveValue = expectedUserBranchPrefs[name];
      Assert.equal(
        gUserBranch.getBoolPref(name),
        effectiveValue,
        "User-branch value for pref has expected value: " + name
      );
    } else {
      effectiveValue = value;
      Assert.ok(
        !gUserBranch.prefHasUserValue(name),
        "User-branch value for pref does not exist: " + name
      );
    }

    // For good measure, check the value returned by UrlbarPrefs.
    Assert.equal(
      UrlbarPrefs.get(name),
      effectiveValue,
      "Effective value for pref is correct: " + name
    );
  }

  Assert.equal(
    UrlbarPrefs.get("quicksuggest.onboardingDialogVersion"),
    onboardingDialogVersion,
    "onboardingDialogVersion"
  );
  Assert.equal(
    UrlbarPrefs.get("quicksuggest.onboardingDialogChoice"),
    onboardingDialogChoice,
    "onboardingDialogChoice"
  );
  Assert.equal(
    TelemetryEnvironment.currentEnvironment.settings.userPrefs[
      "browser.urlbar.quicksuggest.onboardingDialogChoice"
    ],
    onboardingDialogChoice,
    "onboardingDialogChoice is correct in TelemetryEnvironment"
  );

  QuickSuggestTestUtils.assertEvents(telemetryEvents);

  Assert.ok(
    UrlbarPrefs.get("quicksuggest.showedOnboardingDialog"),
    "quicksuggest.showedOnboardingDialog is true after showing dialog"
  );

  // Clean up.
  for (let [name, value] of Object.entries(originalDefaultBranch)) {
    gDefaultBranch.setBoolPref(name, value);
  }
  for (let name of Object.keys(expectedUserBranchPrefs)) {
    gUserBranch.clearUserPref(name);
  }
}

/**
 * Show onbaording dialog.
 *
 * @param {object} [options]
 *   The object options.
 * @param {boolean} [options.skipIntroduction]
 *   If true, return dialog with skipping the introduction section.
 * @returns {{ window, maybeShowPromise: Promise }}
 *   win: window object of the dialog.
 *   maybeShowPromise: Promise of QuickSuggest.maybeShowOnboardingDialog().
 */
async function showOnboardingDialog({ skipIntroduction } = {}) {
  const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
    null,
    QuickSuggest.ONBOARDING_URI,
    { isSubDialog: true }
  );

  const maybeShowPromise = QuickSuggest.maybeShowOnboardingDialog();

  const win = await dialogPromise;
  if (win.document.readyState != "complete") {
    await BrowserTestUtils.waitForEvent(win, "load");
  }

  // Wait until all listers on onboarding dialog are ready.
  await window._quicksuggestOnboardingReady;

  if (!skipIntroduction) {
    return { win, maybeShowPromise };
  }

  // Trigger the transition by pressing Enter on the Next button.
  EventUtils.synthesizeKey("KEY_Enter");

  const introductionSection = win.document.getElementById(
    "introduction-section"
  );
  const mainSection = win.document.getElementById("main-section");

  await BrowserTestUtils.waitForCondition(
    () =>
      BrowserTestUtils.is_hidden(introductionSection) &&
      BrowserTestUtils.is_visible(mainSection)
  );

  return { win, maybeShowPromise };
}

/**
 * Sets all the required prefs for showing the onboarding dialog except for the
 * prefs that are set when the dialog is accepted.
 */
function setDialogPrereqPrefs() {
  UrlbarPrefs.set("quicksuggest.shouldShowOnboardingDialog", true);
  UrlbarPrefs.set("quicksuggest.showedOnboardingDialog", false);
}

/**
 * This is a real hacky way of determining whether the tab key can move focus.
 * Windows and Linux both support it but macOS does not unless full keyboard
 * access is enabled, so practically this is only useful on macOS. Gecko seems
 * to know whether full keyboard access is enabled because it affects focus in
 * Firefox and some code in nsXULElement.cpp and other places mention it, but
 * there doesn't seem to be a way to access that information from JS. There is
 * `Services.focus.elementIsFocusable`, but it returns true regardless of
 * whether full access is enabled.
 *
 * So what we do here is open the dialog and synthesize a tab key. If the focus
 * doesn't change, then we assume moving the focus via the tab key is not
 * supported.
 *
 * Why not just always skip the focus tasks on Mac? Because individual
 * developers (like the one writing this comment) may be running macOS with full
 * keyboard access enabled and want to actually run the tasks on their machines.
 *
 * @returns {boolean}
 */
async function canTabMoveFocus() {
  if (AppConstants.platform != "macosx") {
    return true;
  }

  let canMove = false;
  await doDialogTest({
    callback: async () => {
      const { win, maybeShowPromise } = await showOnboardingDialog({
        skipIntroduction: true,
      });

      let doc = win.document;
      doc.getElementById("onboardingAccept").focus();
      EventUtils.synthesizeKey("KEY_Tab");

      // Whether or not the focus can move to the link.
      canMove = doc.activeElement.id === "onboardingLearnMore";

      EventUtils.synthesizeKey("KEY_Escape");
      await maybeShowPromise;
    },
    onboardingDialogVersion: JSON.stringify({ version: 1 }),
    onboardingDialogChoice: "dismiss_2",
    expectedUserBranchPrefs: {
      "quicksuggest.dataCollection.enabled": false,
    },
    telemetryEvents: [
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "data_collect_toggled",
        object: "disabled",
      },
      {
        category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
        method: "opt_in_dialog",
        object: "dismiss_2",
      },
    ],
  });

  return canMove;
}
