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

requestLongerTimeout(2);

add_task(async function () {
  let win1 = await BrowserTestUtils.openNewBrowserWindow();

  await focusWindow(win1);

  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs"],
      content_scripts: [
        {
          matches: ["http://mochi.test/*/context_tabs_onUpdated_page.html"],
          js: ["content-script.js"],
          run_at: "document_start",
        },
      ],
    },

    background: function () {
      let pageURL =
        "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html";

      let expectedSequence = [
        { status: "loading" },
        { status: "loading", url: pageURL },
        { status: "complete" },
      ];
      let collectedSequence = [];

      browser.tabs.onUpdated.addListener(function (tabId, updatedInfo) {
        // onUpdated also fires with updatedInfo.faviconUrl, so explicitly
        // check for updatedInfo.status before recording the event.
        if ("status" in updatedInfo) {
          collectedSequence.push(updatedInfo);
        }
      });

      browser.runtime.onMessage.addListener(function () {
        if (collectedSequence.length !== expectedSequence.length) {
          browser.test.assertEq(
            JSON.stringify(expectedSequence),
            JSON.stringify(collectedSequence),
            "got unexpected number of updateInfo data"
          );
        } else {
          for (let i = 0; i < expectedSequence.length; i++) {
            browser.test.assertEq(
              expectedSequence[i].status,
              collectedSequence[i].status,
              "check updatedInfo status"
            );
            if (expectedSequence[i].url || collectedSequence[i].url) {
              browser.test.assertEq(
                expectedSequence[i].url,
                collectedSequence[i].url,
                "check updatedInfo url"
              );
            }
          }
        }

        browser.test.notifyPass("tabs.onUpdated");
      });

      browser.tabs.create({ url: pageURL });
    },
    files: {
      "content-script.js": `
        window.addEventListener("message", function(evt) {
          if (evt.data == "frame-updated") {
            browser.runtime.sendMessage("load-completed");
          }
        }, true);
      `,
    },
  });

  await Promise.all([
    extension.startup(),
    extension.awaitFinish("tabs.onUpdated"),
  ]);

  await extension.unload();

  await BrowserTestUtils.closeWindow(win1);
});

async function do_test_update(background, withPermissions = true) {
  let win1 = await BrowserTestUtils.openNewBrowserWindow();

  await focusWindow(win1);

  let manifest = {};
  if (withPermissions) {
    manifest.permissions = ["tabs", "http://mochi.test/"];
  }
  let extension = ExtensionTestUtils.loadExtension({ manifest, background });

  await extension.startup();
  await extension.awaitFinish("finish");

  await extension.unload();

  await BrowserTestUtils.closeWindow(win1);
}

add_task(async function test_pinned() {
  await do_test_update(function background() {
    // Create a new tab for testing update.
    browser.tabs.create({}, function (tab) {
      browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
        // Check callback
        browser.test.assertEq(tabId, tab.id, "Check tab id");
        browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
        if ("pinned" in changeInfo) {
          browser.test.assertTrue(changeInfo.pinned, "Check changeInfo.pinned");
          browser.tabs.onUpdated.removeListener(onUpdated);
          // Remove created tab.
          browser.tabs.remove(tabId);
          browser.test.notifyPass("finish");
        }
      });
      browser.tabs.update(tab.id, { pinned: true });
    });
  });
});

add_task(async function test_unpinned() {
  await do_test_update(function background() {
    // Create a new tab for testing update.
    browser.tabs.create({ pinned: true }, function (tab) {
      browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
        // Check callback
        browser.test.assertEq(tabId, tab.id, "Check tab id");
        browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
        if ("pinned" in changeInfo) {
          browser.test.assertFalse(
            changeInfo.pinned,
            "Check changeInfo.pinned"
          );
          browser.tabs.onUpdated.removeListener(onUpdated);
          // Remove created tab.
          browser.tabs.remove(tabId);
          browser.test.notifyPass("finish");
        }
      });
      browser.tabs.update(tab.id, { pinned: false });
    });
  });
});

add_task(async function test_url() {
  await do_test_update(function background() {
    // Create a new tab for testing update.
    browser.tabs.create({ url: "about:blank?initial_url=1" }, function (tab) {
      const expectedUpdatedURL = "about:blank?updated_url=1";

      browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
        // Wait for the tabs.onUpdated events related to the updated url (because
        // there is a good chance that we may still be receiving events related to
        // the browser.tabs.create API call above before we are able to start
        // loading the new url from the browser.tabs.update API call below).
        if ("url" in changeInfo && changeInfo.url === expectedUpdatedURL) {
          browser.test.assertEq(
            expectedUpdatedURL,
            changeInfo.url,
            "Got tabs.onUpdated event for the expected url"
          );
          browser.tabs.onUpdated.removeListener(onUpdated);
          // Remove created tab.
          browser.tabs.remove(tabId);
          browser.test.notifyPass("finish");
        }
        // Check callback
        browser.test.assertEq(tabId, tab.id, "Check tab id");
        browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
      });

      browser.tabs.update(tab.id, { url: expectedUpdatedURL });
    });
  });
});

add_task(async function test_title() {
  await do_test_update(async function background() {
    const url =
      "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html";
    const tab = await browser.tabs.create({ url });

    browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
      browser.test.assertEq(tabId, tab.id, "Check tab id");
      browser.test.log(`onUpdated: ${JSON.stringify(changeInfo)}`);
      if ("title" in changeInfo && changeInfo.title === "New Message (1)") {
        browser.test.log("changeInfo.title is correct");
        browser.tabs.onUpdated.removeListener(onUpdated);
        browser.tabs.remove(tabId);
        browser.test.notifyPass("finish");
      }
    });

    browser.tabs.executeScript(tab.id, {
      code: "document.title = 'New Message (1)'",
    });
  });
});

add_task(async function test_without_tabs_permission() {
  await do_test_update(async function background() {
    const url =
      "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html";
    let tab = null;
    let count = 0;

    browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
      // An attention change can happen during tabs.create, so
      // we can't compare against tab yet.
      if (!("attention" in changeInfo)) {
        browser.test.assertEq(tabId, tab.id, "Check tab id");
      }
      browser.test.log(`onUpdated: ${JSON.stringify(changeInfo)}`);

      browser.test.assertFalse(
        "url" in changeInfo,
        "url should not be included without tabs permission"
      );
      browser.test.assertFalse(
        "favIconUrl" in changeInfo,
        "favIconUrl should not be included without tabs permission"
      );
      browser.test.assertFalse(
        "title" in changeInfo,
        "title should not be included without tabs permission"
      );

      if (changeInfo.status == "complete") {
        count++;
        if (count === 1) {
          browser.tabs.reload(tabId);
        } else {
          browser.test.log("Reload complete");
          browser.tabs.onUpdated.removeListener(onUpdated);
          browser.tabs.remove(tabId);
          browser.test.notifyPass("finish");
        }
      }
    });

    tab = await browser.tabs.create({ url });
  }, false /* withPermissions */);
});

add_task(async function test_onUpdated_after_onRemoved() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs"],
    },
    async background() {
      const url =
        "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html";
      let removed = false;
      let tab;

      // If remove happens fast and we never receive onUpdated, that is ok, but
      // we never want to receive onUpdated after onRemoved.
      browser.tabs.onUpdated.addListener(function onUpdated(tabId) {
        if (!tab || tab.id !== tabId) {
          return;
        }
        browser.test.assertFalse(
          removed,
          "tab has not been removed before onUpdated"
        );
      });

      browser.tabs.onRemoved.addListener(tabId => {
        if (!tab || tab.id !== tabId) {
          return;
        }
        removed = true;
        browser.test.notifyPass("onRemoved");
      });

      tab = await browser.tabs.create({ url });
      browser.tabs.remove(tab.id);
    },
  });
  await extension.startup();
  await extension.awaitFinish("onRemoved");
  await extension.unload();
});

// Regression test for Bug 1852391.
add_task(async function test_pin_discarded_tab() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs"],
    },
    async background() {
      const url = "http://mochi.test:8888";
      const newTab = await browser.tabs.create({
        url,
        active: false,
        discarded: true,
      });
      browser.tabs.onUpdated.addListener(
        async (tabId, changeInfo) => {
          browser.test.assertEq(
            tabId,
            newTab.id,
            "Expect onUpdated to be fired for the expected tab"
          );
          browser.test.assertEq(
            changeInfo.pinned,
            true,
            "Expect pinned to be set to true"
          );
          await browser.tabs.remove(newTab.id);
          browser.test.notifyPass("onPinned");
        },
        { properties: ["pinned"] }
      );
      await browser.tabs.update(newTab.id, { pinned: true }).catch(err => {
        browser.test.fail(`Got unexpected rejection from tabs.update: ${err}`);
        browser.test.notifyFail("onPinned");
      });
    },
  });

  await extension.startup();
  await extension.awaitFinish("onPinned");
  await extension.unload();
});

add_task(forceGC);
