"use strict";

ChromeUtils.defineESModuleGetters(this, {
  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
  UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(this, "UrlbarTestUtils", () => {
  const { UrlbarTestUtils: module } = ChromeUtils.importESModule(
    "resource://testing-common/UrlbarTestUtils.sys.mjs"
  );
  module.init(this);
  return module;
});

async function loadTipExtension(options = {}) {
  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background() {
      browser.test.onMessage.addListener(options => {
        browser.urlbar.onBehaviorRequested.addListener(query => {
          return "restricting";
        }, "test");
        browser.urlbar.onResultsRequested.addListener(query => {
          return [
            {
              type: "tip",
              source: "local",
              heuristic: true,
              payload: {
                text: "Test",
                buttonText: "OK",
                buttonUrl: options.buttonUrl,
                helpUrl: options.helpUrl,
              },
            },
          ];
        }, "test");
        browser.urlbar.onResultPicked.addListener((payload, details) => {
          browser.test.assertEq(payload.text, "Test", "payload.text");
          browser.test.assertEq(payload.buttonText, "OK", "payload.buttonText");
          browser.test.sendMessage("onResultPicked received", details);
        }, "test");
      });
    },
  });
  await ext.startup();
  ext.sendMessage(options);

  // Wait for the provider to be registered before continuing.  The provider
  // will be registered once the parent process receives the first addListener
  // call from the extension.  There's no better way to do this, unfortunately.
  // For example, if the extension sends a message to the test after it adds its
  // listeners and then we wait here for that message, there's no guarantee that
  // the addListener calls will have been received in the parent yet.
  await BrowserTestUtils.waitForCondition(
    () => UrlbarProvidersManager.getProvider("test"),
    "Waiting for provider to be registered"
  );

  Assert.ok(
    UrlbarProvidersManager.getProvider("test"),
    "Provider should have been registered"
  );
  return ext;
}

/**
 * Updates the Top Sites feed.
 *
 * @param {Function} condition
 *   A callback that returns true after Top Sites are successfully updated.
 * @param {boolean} searchShortcuts
 *   True if Top Sites search shortcuts should be enabled.
 */
async function updateTopSites(condition, searchShortcuts = false) {
  // Toggle the pref to clear the feed cache and force an update.
  await SpecialPowers.pushPrefEnv({
    set: [
      ["browser.newtabpage.activity-stream.feeds.system.topsites", false],
      ["browser.newtabpage.activity-stream.feeds.system.topsites", true],
      [
        "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
        searchShortcuts,
      ],
    ],
  });

  // Wait for the feed to be updated.
  await TestUtils.waitForCondition(() => {
    let sites = AboutNewTab.getTopSites();
    return condition(sites);
  }, "Waiting for top sites to be updated");
}

add_setup(async function () {
  Services.prefs.setBoolPref("browser.urlbar.suggest.quickactions", false);
  registerCleanupFunction(async () => {
    Services.prefs.clearUserPref("browser.urlbar.suggest.quickactions");
  });
  // Set the notification timeout to a really high value to avoid intermittent
  // failures due to the mock extensions not responding in time.
  await SpecialPowers.pushPrefEnv({
    set: [["browser.urlbar.extension.timeout", 5000]],
  });
});

// Loads a tip extension without a main button URL and presses enter on the main
// button.
add_task(async function tip_onResultPicked_mainButton_noURL_enter() {
  let ext = await loadTipExtension();
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: "test",
  });
  EventUtils.synthesizeKey("KEY_Enter");
  await ext.awaitMessage("onResultPicked received");
  await ext.unload();
});

// Loads a tip extension without a main button URL and clicks the main button.
add_task(async function tip_onResultPicked_mainButton_noURL_mouse() {
  let ext = await loadTipExtension();
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: "test",
  });
  let mainButton = gURLBar.querySelector(".urlbarView-button-tip");
  Assert.ok(mainButton);
  EventUtils.synthesizeMouseAtCenter(mainButton, {});
  await ext.awaitMessage("onResultPicked received");
  await ext.unload();
});

// Loads a tip extension with a main button URL and presses enter on the main
// button.
add_task(async function tip_onResultPicked_mainButton_url_enter() {
  let ext = await loadTipExtension({ buttonUrl: "http://example.com/" });
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      waitForFocus,
      value: "test",
    });
    let loadedPromise = BrowserTestUtils.browserLoaded(
      gBrowser.selectedBrowser
    );
    ext.onMessage("onResultPicked received", () => {
      Assert.ok(false, "onResultPicked should not be called");
    });
    EventUtils.synthesizeKey("KEY_Enter");
    await loadedPromise;
    Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
  });
  await ext.unload();
});

// Loads a tip extension with a main button URL and clicks the main button.
add_task(async function tip_onResultPicked_mainButton_url_mouse() {
  let ext = await loadTipExtension({ buttonUrl: "http://example.com/" });
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      waitForFocus,
      value: "test",
    });
    let mainButton = gURLBar.querySelector(".urlbarView-button-tip");
    Assert.ok(mainButton);
    let loadedPromise = BrowserTestUtils.browserLoaded(
      gBrowser.selectedBrowser
    );
    ext.onMessage("onResultPicked received", () => {
      Assert.ok(false, "onResultPicked should not be called");
    });
    EventUtils.synthesizeMouseAtCenter(mainButton, {});
    await loadedPromise;
    Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
  });
  await ext.unload();
});

// Loads a tip extension with a help button URL and presses enter on the help
// button.
add_task(async function tip_onResultPicked_helpButton_url_enter() {
  let ext = await loadTipExtension({ helpUrl: "http://example.com/" });
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      waitForFocus,
      value: "test",
    });
    ext.onMessage("onResultPicked received", () => {
      Assert.ok(false, "onResultPicked should not be called");
    });
    let loadedPromise = BrowserTestUtils.browserLoaded(
      gBrowser.selectedBrowser
    );
    if (UrlbarPrefs.get("resultMenu")) {
      await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "h");
    } else {
      EventUtils.synthesizeKey("KEY_Tab");
      EventUtils.synthesizeKey("KEY_Enter");
    }
    info("Waiting for help URL to load");
    await loadedPromise;
    Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
  });
  await ext.unload();
});

// Loads a tip extension with a help button URL and clicks the help button.
add_task(async function tip_onResultPicked_helpButton_url_mouse() {
  let ext = await loadTipExtension({ helpUrl: "http://example.com/" });
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      waitForFocus,
      value: "test",
    });
    ext.onMessage("onResultPicked received", () => {
      Assert.ok(false, "onResultPicked should not be called");
    });
    let loadedPromise = BrowserTestUtils.browserLoaded(
      gBrowser.selectedBrowser
    );
    if (UrlbarPrefs.get("resultMenu")) {
      await UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "h", {
        openByMouse: true,
      });
    } else {
      let helpButton = gURLBar.querySelector(".urlbarView-button-help");
      Assert.ok(helpButton);
      EventUtils.synthesizeMouseAtCenter(helpButton, {});
    }
    info("Waiting for help URL to load");
    await loadedPromise;
    Assert.equal(gBrowser.currentURI.spec, "http://example.com/");
  });
  await ext.unload();
});

// Tests the search function with a non-empty string.
add_task(async function search() {
  gURLBar.blur();

  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background: () => {
      browser.urlbar.search("test");
    },
  });
  await ext.startup();

  let context = await UrlbarTestUtils.promiseSearchComplete(window);
  Assert.equal(gURLBar.value, "test");
  Assert.equal(context.searchString, "test");
  Assert.ok(gURLBar.focused);
  Assert.equal(gURLBar.getAttribute("focused"), "true");

  await UrlbarTestUtils.promisePopupClose(window);
  await ext.unload();
});

// Tests the search function with an empty string.
add_task(async function searchEmpty() {
  gURLBar.blur();

  // Searching for an empty string shows the history view, but there may be no
  // history here since other tests may have cleared it or since this test is
  // running in isolation.  We want to make sure providers are called and their
  // results are shown, so add a provider that returns a tip.
  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background() {
      browser.urlbar.onBehaviorRequested.addListener(query => {
        return "restricting";
      }, "test");
      browser.urlbar.onResultsRequested.addListener(query => {
        return [
          {
            type: "tip",
            source: "local",
            heuristic: true,
            payload: {
              text: "Test",
              buttonText: "OK",
            },
          },
        ];
      }, "test");
      browser.urlbar.search("");
    },
  });
  await ext.startup();

  await BrowserTestUtils.waitForCondition(
    () => UrlbarProvidersManager.getProvider("test"),
    "Waiting for provider to be registered"
  );

  let context = await UrlbarTestUtils.promiseSearchComplete(window);
  Assert.equal(gURLBar.value, "");
  Assert.equal(context.searchString, "");
  Assert.equal(context.results.length, 1);
  Assert.equal(context.results[0].type, UrlbarUtils.RESULT_TYPE.TIP);
  Assert.ok(gURLBar.focused);
  Assert.equal(gURLBar.getAttribute("focused"), "true");

  await UrlbarTestUtils.promisePopupClose(window);
  await ext.unload();
  await SpecialPowers.popPrefEnv();
});

// Tests the search function with `focus: false`.
add_task(async function searchFocusFalse() {
  await PlacesUtils.history.clear();
  await PlacesUtils.bookmarks.eraseEverything();
  await PlacesTestUtils.addVisits([
    "http://example.com/test1",
    "http://example.com/test2",
  ]);

  gURLBar.blur();

  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background: () => {
      browser.urlbar.search("test", { focus: false });
    },
  });
  await ext.startup();

  let context = await UrlbarTestUtils.promiseSearchComplete(window);
  Assert.equal(gURLBar.value, "test");
  Assert.equal(context.searchString, "test");
  Assert.ok(!gURLBar.focused);
  Assert.ok(!gURLBar.hasAttribute("focused"));

  let resultCount = UrlbarTestUtils.getResultCount(window);
  Assert.equal(resultCount, 3);

  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.SEARCH);
  Assert.equal(result.title, "test");

  result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
  Assert.equal(result.url, "http://example.com/test2");

  result = await UrlbarTestUtils.getDetailsOfResultAt(window, 2);
  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
  Assert.equal(result.url, "http://example.com/test1");

  await UrlbarTestUtils.promisePopupClose(window);
  await ext.unload();
  await SpecialPowers.popPrefEnv();
});

// Tests the search function with `focus: false` and an empty string.
add_task(async function searchFocusFalseEmpty() {
  await PlacesUtils.history.clear();
  await PlacesUtils.bookmarks.eraseEverything();
  for (let i = 0; i < 5; i++) {
    await PlacesTestUtils.addVisits(["http://example.com/test1"]);
  }
  await updateTopSites(sites => sites.length == 1);
  gURLBar.blur();

  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background: () => {
      browser.urlbar.search("", { focus: false });
    },
  });
  await ext.startup();

  let context = await UrlbarTestUtils.promiseSearchComplete(window);
  Assert.equal(gURLBar.value, "");
  Assert.equal(context.searchString, "");
  Assert.ok(!gURLBar.focused);
  Assert.ok(!gURLBar.hasAttribute("focused"));

  let resultCount = UrlbarTestUtils.getResultCount(window);
  Assert.equal(resultCount, 1);

  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
  Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.URL);
  Assert.equal(result.url, "http://example.com/test1");

  await UrlbarTestUtils.promisePopupClose(window);
  await ext.unload();
  await SpecialPowers.popPrefEnv();
});

// Tests the focus function with select = false.
add_task(async function focusSelectFalse() {
  gURLBar.blur();
  gURLBar.value = "test";
  Assert.ok(!gURLBar.focused);
  Assert.ok(!gURLBar.hasAttribute("focused"));

  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background: () => {
      browser.urlbar.focus();
    },
  });
  await ext.startup();

  await TestUtils.waitForCondition(() => gURLBar.focused);
  Assert.ok(gURLBar.focused);
  Assert.ok(gURLBar.hasAttribute("focused"));
  Assert.equal(gURLBar.selectionStart, gURLBar.selectionEnd);

  await ext.unload();
});

// Tests the focus function with select = true.
add_task(async function focusSelectTrue() {
  gURLBar.blur();
  gURLBar.value = "test";
  Assert.ok(!gURLBar.focused);
  Assert.ok(!gURLBar.hasAttribute("focused"));

  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background: () => {
      browser.urlbar.focus(true);
    },
  });
  await ext.startup();

  await TestUtils.waitForCondition(() => gURLBar.focused);
  Assert.ok(gURLBar.focused);
  Assert.ok(gURLBar.hasAttribute("focused"));
  Assert.equal(gURLBar.selectionStart, 0);
  Assert.equal(gURLBar.selectionEnd, "test".length);

  await ext.unload();
});

// Tests the closeView function.
add_task(async function closeView() {
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: "test",
  });

  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background: () => {
      browser.urlbar.closeView();
    },
  });
  await UrlbarTestUtils.promisePopupClose(window, () => ext.startup());
  await ext.unload();
});

// Tests the onEngagement events.
add_task(async function onEngagement() {
  gURLBar.blur();

  // Enable engagement telemetry.
  Services.prefs.setBoolPref("browser.urlbar.eventTelemetry.enabled", true);

  let ext = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["urlbar"],
    },
    isPrivileged: true,
    background() {
      browser.urlbar.onEngagement.addListener(state => {
        browser.test.sendMessage("onEngagement", state);
      }, "test");
      browser.urlbar.onBehaviorRequested.addListener(query => {
        return "restricting";
      }, "test");
      browser.urlbar.onResultsRequested.addListener(query => {
        return [
          {
            type: "tip",
            source: "local",
            heuristic: true,
            payload: {
              text: "Test",
              buttonText: "OK",
            },
          },
        ];
      }, "test");
      browser.urlbar.search("");
    },
  });
  await ext.startup();

  // Start an engagement.
  let messagePromise = ext.awaitMessage("onEngagement");
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: "test",
    fireInputEvent: true,
  });
  let state = await messagePromise;
  Assert.equal(state, "start");

  // Abandon the engagement.
  messagePromise = ext.awaitMessage("onEngagement");
  gURLBar.blur();
  state = await messagePromise;
  Assert.equal(state, "abandonment");

  // Start an engagement.
  messagePromise = ext.awaitMessage("onEngagement");
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: "test",
    fireInputEvent: true,
  });
  state = await messagePromise;
  Assert.equal(state, "start");

  // End the engagement by pressing enter on the extension's tip result.
  messagePromise = ext.awaitMessage("onEngagement");
  EventUtils.synthesizeKey("KEY_Enter");
  state = await messagePromise;
  Assert.equal(state, "engagement");

  // We'll open about:preferences next.  Since it won't open in a new tab if the
  // current tab is blank, open a new tab now.
  await BrowserTestUtils.withNewTab("about:blank", async () => {
    // Start an engagement.
    messagePromise = ext.awaitMessage("onEngagement");
    await UrlbarTestUtils.promiseAutocompleteResultPopup({
      window,
      waitForFocus,
      value: "test",
      fireInputEvent: true,
    });
    state = await messagePromise;
    Assert.equal(state, "start");

    // Press up and enter to pick the search settings button.
    messagePromise = ext.awaitMessage("onEngagement");
    EventUtils.synthesizeKey("KEY_ArrowUp");
    EventUtils.synthesizeKey("KEY_Enter");
    await BrowserTestUtils.browserLoaded(
      gBrowser.selectedBrowser,
      false,
      "about:preferences#search"
    );
    state = await messagePromise;
    Assert.equal(state, "discard");
  });

  // Start a final engagement to make sure the previous discard didn't mess
  // anything up.
  messagePromise = ext.awaitMessage("onEngagement");
  await UrlbarTestUtils.promiseAutocompleteResultPopup({
    window,
    waitForFocus,
    value: "test",
    fireInputEvent: true,
  });
  state = await messagePromise;
  Assert.equal(state, "start");

  // End the engagement by pressing enter on the extension's tip result.
  messagePromise = ext.awaitMessage("onEngagement");
  EventUtils.synthesizeKey("KEY_Enter");
  state = await messagePromise;
  Assert.equal(state, "engagement");

  await ext.unload();
  Services.prefs.clearUserPref("browser.urlbar.eventTelemetry.enabled");
});
