<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Tabs executeScript runAt Test</title>
  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
  <script type="text/javascript" src="head.js"></script>
  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
</head>
<body>

<script type="text/javascript">
"use strict";

/**
 * These tests ensure that the runAt argument to tabs.executeScript delays
 * script execution until the document has reached the correct state.
 *
 * Since tests of this nature are especially race-prone, it relies on a
 * server-JS script to delay the completion of our test page's load cycle long
 * enough for us to attempt to load our scripts in the earlies phase we support.
 *
 * And since we can't actually rely on that timing, it retries any attempts that
 * fail to load as early as expected, but don't load at any illegal time.
 */

add_task(async function testExecuteScript() {
  const win = window.open("about:blank");

  async function background(DEBUG) {
    let tab;

    const BASE = "http://mochi.test:8888/tests/mobile/android/components/extensions/test/mochitest/";
    const URL = BASE + "file_slowed_document.sjs";

    const MAX_TRIES = 30;

    const onUpdatedPromise = (tabId, url, status) => {
      return new Promise(resolve => {
        browser.tabs.onUpdated.addListener(function listener(_, changed, tab) {
          if (tabId == tab.id && changed.status == status && tab.url == url) {
            browser.tabs.onUpdated.removeListener(listener);
            resolve();
          }
        });
      });
    };

    try {
      [tab] = await browser.tabs.query({active: true, currentWindow: true});

      let success = false;
      for (let tries = 0; !success && tries < MAX_TRIES; tries++) {
        const url = `${URL}?with-iframe&r=${Math.random()}`;

        const loadingPromise = onUpdatedPromise(tab.id, url, "loading");
        const completePromise = onUpdatedPromise(tab.id, url, "complete");

        // TODO: Test allFrames and frameId.

        await browser.tabs.update({url});
        await loadingPromise;

        const states = await Promise.all([
          // Send the executeScript requests in the reverse order that we expect
          // them to execute in, to avoid them passing only because of timing
          // races.
          browser.tabs.executeScript({
            code: "document.readyState",
            runAt: "document_idle",
          }),
          browser.tabs.executeScript({
            code: "document.readyState",
            runAt: "document_end",
          }),
          browser.tabs.executeScript({
            code: "document.readyState",
            runAt: "document_start",
          }),
        ].reverse());

        browser.test.log(`Got states: ${states}`);

        // Make sure that none of our scripts executed earlier than expected,
        // regardless of retries.
        browser.test.assertTrue(states[1] == "interactive" || states[1] == "complete",
                                `document_end state is valid: ${states[1]}`);
        browser.test.assertTrue(states[2] == "interactive" || states[2] == "complete",
                                `document_idle state is valid: ${states[2]}`);

        // If we have the earliest valid states for each script, we're done.
        // Otherwise, try again.
        success = ((states[0] == "loading" || DEBUG) &&
                   states[1] == "interactive" &&
                   (states[2] == "interactive" || states[2] == "complete"));

        await completePromise;
      }

      browser.test.assertTrue(success, "Got the earliest expected states at least once");

      browser.test.notifyPass("executeScript-runAt");
    } catch (e) {
      browser.test.fail(`Error: ${e} :: ${e.stack}`);
      browser.test.notifyFail("executeScript-runAt");
    }
  }

  const extension = ExtensionTestUtils.loadExtension({
    manifest: {
      "permissions": ["http://mochi.test/", "tabs"],
    },
    background: `(${background})(${AppConstants.DEBUG})`,
  });

  await extension.startup();

  await extension.awaitFinish("executeScript-runAt");

  await extension.unload();

  win.close();
});
</script>

</body>
</html>
