"use strict";

const server = createHttpServer();
server.registerDirectory("/data/", do_get_file("data"));

const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;

// ExtensionContent.jsm needs to know when it's running from xpcshell,
// to use the right timeout for content scripts executed at document_idle.
ExtensionTestUtils.mockAppInfo();

add_task(async function test_contentscript_runAt() {
  function background() {
    browser.runtime.onMessage.addListener(
      ([msg, expectedStates, readyState]) => {
        if (msg == "chrome-namespace-ok") {
          browser.test.sendMessage(msg);
          return;
        }

        browser.test.assertEq("script-run", msg, "message type is correct");
        browser.test.assertTrue(
          expectedStates.includes(readyState),
          `readyState "${readyState}" is one of [${expectedStates}]`
        );
        browser.test.sendMessage("script-run-" + expectedStates[0]);
      }
    );
  }

  function contentScriptStart() {
    browser.runtime.sendMessage([
      "script-run",
      ["loading"],
      document.readyState,
    ]);
  }
  function contentScriptEnd() {
    browser.runtime.sendMessage([
      "script-run",
      ["interactive", "complete"],
      document.readyState,
    ]);
  }
  function contentScriptIdle() {
    browser.runtime.sendMessage([
      "script-run",
      ["complete"],
      document.readyState,
    ]);
  }

  function contentScript() {
    let manifest = browser.runtime.getManifest();
    void manifest.applications.gecko.id;
    browser.runtime.sendMessage(["chrome-namespace-ok"]);
  }

  let extensionData = {
    manifest: {
      browser_specific_settings: {
        gecko: { id: "contentscript@tests.mozilla.org" },
      },
      content_scripts: [
        {
          matches: ["http://*/*/file_sample.html"],
          js: ["content_script_start.js"],
          run_at: "document_start",
        },
        {
          matches: ["http://*/*/file_sample.html"],
          js: ["content_script_end.js"],
          run_at: "document_end",
        },
        {
          matches: ["http://*/*/file_sample.html"],
          js: ["content_script_idle.js"],
          run_at: "document_idle",
        },
        {
          matches: ["http://*/*/file_sample.html"],
          js: ["content_script_idle.js"],
          // Test default `run_at`.
        },
        {
          matches: ["http://*/*/file_sample.html"],
          js: ["content_script.js"],
          run_at: "document_idle",
        },
      ],
    },
    background,

    files: {
      "content_script_start.js": contentScriptStart,
      "content_script_end.js": contentScriptEnd,
      "content_script_idle.js": contentScriptIdle,
      "content_script.js": contentScript,
    },
  };

  let extension = ExtensionTestUtils.loadExtension(extensionData);

  let loadingCount = 0;
  let interactiveCount = 0;
  let completeCount = 0;
  extension.onMessage("script-run-loading", () => {
    loadingCount++;
  });
  extension.onMessage("script-run-interactive", () => {
    interactiveCount++;
  });

  let completePromise = new Promise(resolve => {
    extension.onMessage("script-run-complete", () => {
      completeCount++;
      if (completeCount > 1) {
        resolve();
      }
    });
  });

  let chromeNamespacePromise = extension.awaitMessage("chrome-namespace-ok");

  await extension.startup();

  let contentPage = await ExtensionTestUtils.loadContentPage(
    `${BASE_URL}/file_sample.html`
  );

  await Promise.all([completePromise, chromeNamespacePromise]);

  await contentPage.close();

  equal(loadingCount, 1, "document_start script ran exactly once");
  equal(interactiveCount, 1, "document_end script ran exactly once");
  equal(completeCount, 2, "document_idle script ran exactly twice");

  await extension.unload();
});

add_task(async function test_contentscript_window_open() {
  if (AppConstants.DEBUG && Services.appinfo.browserTabsRemoteAutostart) {
    return;
  }

  let script = async () => {
    /* globals x */
    browser.test.assertEq(1, x, "Should only run once");

    if (top !== window) {
      // Wait for our parent page to load, then set a timeout to wait for the
      // document.open call, so we make sure to not tear down the extension
      // until after we've done the document.open.
      await new Promise(resolve => {
        top.addEventListener("load", () => setTimeout(resolve, 0), {
          once: true,
        });
      });
    }

    browser.test.sendMessage("content-script", [location.href, top === window]);
  };

  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: {
        gecko: { id: "contentscript@tests.mozilla.org" },
      },
      content_scripts: [
        {
          matches: ["<all_urls>"],
          js: ["content_script.js"],
          run_at: "document_start",
          match_about_blank: true,
          all_frames: true,
        },
      ],
    },

    files: {
      "content_script.js": `
        var x = (x || 0) + 1;
        (${script})();
      `,
    },
  });

  await extension.startup();

  let url = `${BASE_URL}/file_document_open.html`;
  let contentPage = await ExtensionTestUtils.loadContentPage(url);

  let [pageURL, pageIsTop] = await extension.awaitMessage("content-script");

  // Sometimes we get a content script load for the initial about:blank
  // top level frame here, sometimes we don't. Either way is fine, as long as we
  // don't get two loads into the same document.open() document.
  if (pageURL === "about:blank") {
    equal(pageIsTop, true);
    [pageURL, pageIsTop] = await extension.awaitMessage("content-script");
  }

  Assert.deepEqual([pageURL, pageIsTop], [url, true]);

  let [frameURL, isTop] = await extension.awaitMessage("content-script");
  Assert.deepEqual([frameURL, isTop], [url, false]);

  await contentPage.close();
  await extension.unload();
});

// This test verify that a cached script is still able to catch the document
// while it is still loading (when we do not block the document parsing as
// we do for a non cached script).
add_task(async function test_cached_contentscript_on_document_start() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      content_scripts: [
        {
          matches: ["http://localhost/*/file_document_open.html"],
          js: ["content_script.js"],
          run_at: "document_start",
        },
      ],
    },

    files: {
      "content_script.js": `
        browser.test.sendMessage("content-script-loaded", {
          url: window.location.href,
          documentReadyState: document.readyState,
        });
      `,
    },
  });

  await extension.startup();

  let url = `${BASE_URL}/file_document_open.html`;
  let contentPage = await ExtensionTestUtils.loadContentPage(url);

  let msg = await extension.awaitMessage("content-script-loaded");
  Assert.deepEqual(
    msg,
    {
      url,
      documentReadyState: "loading",
    },
    "Got the expected url and document.readyState from a non cached script"
  );

  // Reload the page and check that the cached content script is still able to
  // run on document_start.
  await contentPage.loadURL(url);

  let msgFromCached = await extension.awaitMessage("content-script-loaded");
  Assert.deepEqual(
    msgFromCached,
    {
      url,
      documentReadyState: "loading",
    },
    "Got the expected url and document.readyState from a cached script"
  );

  await extension.unload();

  await contentPage.close();
});
