/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

const { AddonTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/AddonTestUtils.sys.mjs"
);

const SEARCH_TERM = "test";
const SEARCH_URL = "https://example.org/?q={searchTerms}";

AddonTestUtils.initMochitest(this);

add_task(async function test_search() {
  async function background(SEARCH_TERM) {
    browser.test.onMessage.addListener(async (msg, tabIds) => {
      if (msg !== "removeTabs") {
        return;
      }

      await browser.tabs.remove(tabIds);
      browser.test.sendMessage("onTabsRemoved");
    });

    function awaitSearchResult() {
      return new Promise(resolve => {
        async function listener(tabId, info, changedTab) {
          if (changedTab.url == "about:blank") {
            // Ignore events related to the initial tab open.
            return;
          }

          if (info.status === "complete") {
            browser.tabs.onUpdated.removeListener(listener);
            resolve({ tabId, url: changedTab.url });
          }
        }

        browser.tabs.onUpdated.addListener(listener);
      });
    }

    let engines = await browser.search.get();
    browser.test.sendMessage("engines", engines);

    // Search with no tabId
    browser.search.search({ query: SEARCH_TERM + "1", engine: "Search Test" });
    let result = await awaitSearchResult();
    browser.test.sendMessage("searchLoaded", result);

    // Search with tabId
    let tab = await browser.tabs.create({});
    browser.search.search({
      query: SEARCH_TERM + "2",
      engine: "Search Test",
      tabId: tab.id,
    });
    result = await awaitSearchResult();
    browser.test.assertEq(result.tabId, tab.id, "Page loaded in right tab");
    browser.test.sendMessage("searchLoaded", result);
  }

  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["search", "tabs"],
      chrome_settings_overrides: {
        search_provider: {
          name: "Search Test",
          search_url: SEARCH_URL,
        },
      },
    },
    background: `(${background})("${SEARCH_TERM}")`,
    useAddonManager: "temporary",
  });
  await extension.startup();
  await AddonTestUtils.waitForSearchProviderStartup(extension);

  let addonEngines = await extension.awaitMessage("engines");
  let engines = (await Services.search.getEngines()).filter(
    engine => !engine.hidden
  );
  is(addonEngines.length, engines.length, "Engine lengths are the same.");
  let defaultEngine = addonEngines.filter(engine => engine.isDefault === true);
  is(defaultEngine.length, 1, "One default engine");
  is(
    defaultEngine[0].name,
    (await Services.search.getDefault()).name,
    "Default engine is correct"
  );

  const result1 = await extension.awaitMessage("searchLoaded");
  is(
    result1.url,
    SEARCH_URL.replace("{searchTerms}", SEARCH_TERM + "1"),
    "Loaded page matches search"
  );
  await TestUtils.waitForCondition(
    () => !gURLBar.focused,
    "Wait for unfocusing the urlbar"
  );
  info("The urlbar has no focus when searching without tabId");

  const result2 = await extension.awaitMessage("searchLoaded");
  is(
    result2.url,
    SEARCH_URL.replace("{searchTerms}", SEARCH_TERM + "2"),
    "Loaded page matches search"
  );
  await TestUtils.waitForCondition(
    () => !gURLBar.focused,
    "Wait for unfocusing the urlbar"
  );
  info("The urlbar has no focus when searching with tabId");

  extension.sendMessage("removeTabs", [result1.tabId, result2.tabId]);
  await extension.awaitMessage("onTabsRemoved");

  await extension.unload();
});

add_task(async function test_search_default_engine() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["search"],
    },
    background() {
      browser.test.onMessage.addListener((msg, tabId) => {
        browser.test.assertEq(msg, "search");
        browser.search.search({ query: "searchTermForDefaultEngine", tabId });
      });
      browser.test.sendMessage("extension-origin", browser.runtime.getURL("/"));
    },
    useAddonManager: "temporary",
  });

  // Use another extension to intercept and block the search request,
  // so that there is no outbound network activity that would kill the test.
  // This method also allows us to verify that:
  // 1) the search appears as a normal request in the webRequest API.
  // 2) the request is associated with the triggering extension.
  let extensionWithObserver = ExtensionTestUtils.loadExtension({
    manifest: { permissions: ["webRequest", "webRequestBlocking", "*://*/*"] },
    async background() {
      let tab = await browser.tabs.create({ url: "about:blank" });
      browser.webRequest.onBeforeRequest.addListener(
        details => {
          browser.test.log(`Intercepted request ${JSON.stringify(details)}`);
          browser.tabs.remove(tab.id).then(() => {
            browser.test.sendMessage("detectedSearch", details);
          });
          return { cancel: true };
        },
        {
          tabId: tab.id,
          types: ["main_frame"],
          urls: ["*://*/*"],
        },
        ["blocking"]
      );
      browser.test.sendMessage("ready", tab.id);
    },
  });
  await extension.startup();
  const EXPECTED_ORIGIN = await extension.awaitMessage("extension-origin");

  await extensionWithObserver.startup();
  let tabId = await extensionWithObserver.awaitMessage("ready");

  extension.sendMessage("search", tabId);
  let requestDetails =
    await extensionWithObserver.awaitMessage("detectedSearch");
  await extension.unload();
  await extensionWithObserver.unload();

  ok(
    requestDetails.url.includes("searchTermForDefaultEngine"),
    `Expected search term in ${requestDetails.url}`
  );
  is(
    requestDetails.originUrl,
    EXPECTED_ORIGIN,
    "Search request's should be associated with the originating extension."
  );
});

add_task(async function test_search_disposition() {
  async function background() {
    let resolvers = {};

    function tabListener(tabId, changeInfo, tab) {
      if (tab.url == "about:blank") {
        // Ignore events related to the initial tab open.
        return;
      }

      if (changeInfo.status === "complete") {
        let query = new URL(tab.url).searchParams.get("q");
        let resolver = resolvers[query];
        browser.test.assertTrue(resolver, `Found resolver for ${tab.url}`);
        browser.test.assertTrue(
          resolver.resolve,
          `${query} was not resolved yet`
        );
        resolver.resolve({
          tabId,
          windowId: tab.windowId,
        });
        resolver.resolve = null; // resolve can be used only once.
      }
    }
    browser.tabs.onUpdated.addListener(tabListener);

    async function awaitSearchResult(args) {
      resolvers[args.query] = {};
      resolvers[args.query].promise = new Promise(
        _resolve => (resolvers[args.query].resolve = _resolve)
      );
      await browser.search.search({ ...args, engine: "Search Test" });
      let searchResult = await resolvers[args.query].promise;
      return searchResult;
    }

    const firstTab = await browser.tabs.create({
      active: true,
      url: "about:blank",
    });

    // Search in new tab (testing default disposition)
    let result = await awaitSearchResult({
      query: "DefaultDisposition",
    });
    browser.test.assertFalse(
      result.tabId === firstTab.id,
      "Query ran in new tab"
    );
    browser.test.assertEq(
      result.windowId,
      firstTab.windowId,
      "Query ran in current window"
    );
    await browser.tabs.remove(result.tabId); // Cleanup

    // Search in new tab
    result = await awaitSearchResult({
      query: "NewTab",
      disposition: "NEW_TAB",
    });
    browser.test.assertFalse(
      result.tabId === firstTab.id,
      "Query ran in new tab"
    );
    browser.test.assertEq(
      result.windowId,
      firstTab.windowId,
      "Query ran in current window"
    );
    await browser.tabs.remove(result.tabId); // Cleanup

    // Search in current tab
    result = await awaitSearchResult({
      query: "CurrentTab",
      disposition: "CURRENT_TAB",
    });
    browser.test.assertDeepEq(
      {
        tabId: firstTab.id,
        windowId: firstTab.windowId,
      },
      result,
      "Query ran in current tab in current window"
    );

    // Search in a specific tab
    let newTab = await browser.tabs.create({
      active: false,
      url: "about:blank",
    });
    result = await awaitSearchResult({
      query: "SpecificTab",
      tabId: newTab.id,
    });
    browser.test.assertDeepEq(
      {
        tabId: newTab.id,
        windowId: firstTab.windowId,
      },
      result,
      "Query ran in specific tab in current window"
    );
    await browser.tabs.remove(newTab.id); // Cleanup

    // Search in a new window
    result = await awaitSearchResult({
      query: "NewWindow",
      disposition: "NEW_WINDOW",
    });
    browser.test.assertFalse(
      result.windowId === firstTab.windowId,
      "Query ran in new window"
    );
    await browser.windows.remove(result.windowId); // Cleanup
    await browser.tabs.remove(firstTab.id); // Cleanup

    // Make sure tabId and disposition can't be used together
    await browser.test.assertRejects(
      browser.search.search({
        query: " ",
        tabId: 1,
        disposition: "NEW_WINDOW",
      }),
      "Cannot set both 'disposition' and 'tabId'",
      "Should not be able to set both tabId and disposition"
    );

    // Make sure we reject if an invalid tabId is used
    await browser.test.assertRejects(
      browser.search.search({
        query: " ",
        tabId: Number.MAX_SAFE_INTEGER,
      }),
      /Invalid tab ID/,
      "Should not be able to set an invalid tabId"
    );

    browser.test.notifyPass("disposition");
  }
  let searchExtension = ExtensionTestUtils.loadExtension({
    manifest: {
      chrome_settings_overrides: {
        search_provider: {
          name: "Search Test",
          search_url: "https://example.org/?q={searchTerms}",
        },
      },
    },
    useAddonManager: "temporary",
  });
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["search", "tabs"],
    },
    background,
  });
  await searchExtension.startup();
  await extension.startup();
  await extension.awaitFinish("disposition");
  await extension.unload();
  await searchExtension.unload();
});
