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

function getBrowserAction(extension) {
  const {
    global: { browserActionFor },
  } = Management;

  let ext = WebExtensionPolicy.getByID(extension.id)?.extension;
  return browserActionFor(ext);
}

async function assertViewCount(extension, count, waitForPromise) {
  let ext = WebExtensionPolicy.getByID(extension.id).extension;

  if (waitForPromise) {
    await waitForPromise;
  }

  is(
    ext.views.size,
    count,
    "Should have the expected number of extension views"
  );
}

function promiseExtensionPageClosed(extension, viewType) {
  return new Promise(resolve => {
    const policy = WebExtensionPolicy.getByID(extension.id);

    const listener = (evtType, context) => {
      if (context.viewType === viewType) {
        policy.extension.off("extension-proxy-context-load", listener);

        context.callOnClose({
          close: resolve,
        });
      }
    };
    policy.extension.on("extension-proxy-context-load", listener);
  });
}

let scriptPage = url =>
  `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>${url}</body></html>`;

async function testInArea(area) {
  let extension = ExtensionTestUtils.loadExtension({
    background() {
      let middleClickShowPopup = false;
      browser.browserAction.onClicked.addListener((tabs, info) => {
        browser.test.sendMessage("browserAction-onClicked");
        if (info.button === 1 && middleClickShowPopup) {
          browser.browserAction.openPopup();
        }
      });

      browser.test.onMessage.addListener(async msg => {
        if (msg.type === "setBrowserActionPopup") {
          let opts = { popup: msg.popup };
          if (msg.onCurrentWindowId) {
            let { id } = await browser.windows.getCurrent();
            opts = { ...opts, windowId: id };
          } else if (msg.onActiveTabId) {
            let [{ id }] = await browser.tabs.query({
              active: true,
              currentWindow: true,
            });
            opts = { ...opts, tabId: id };
          }
          await browser.browserAction.setPopup(opts);
          browser.test.sendMessage("setBrowserActionPopup:done");
        } else if (msg.type === "setMiddleClickShowPopup") {
          middleClickShowPopup = msg.show;
          browser.test.sendMessage("setMiddleClickShowPopup:done");
        }
      });

      browser.test.sendMessage("background-page-ready");
    },
    manifest: {
      browser_action: {
        default_popup: "popup-a.html",
        browser_style: true,
      },
    },

    files: {
      "popup-a.html": scriptPage("popup-a.js"),
      "popup-a.js": function () {
        browser.test.onMessage.addListener(msg => {
          if (msg == "close-popup-using-window.close") {
            window.close();
          }
        });

        window.onload = () => {
          let color = window.getComputedStyle(document.body).color;
          browser.test.assertEq("rgb(34, 36, 38)", color);
          browser.test.sendMessage("from-popup", "popup-a");
        };
      },

      "data/popup-b.html": scriptPage("popup-b.js"),
      "data/popup-b.js": function () {
        window.onload = () => {
          browser.test.sendMessage("from-popup", "popup-b");
        };
      },

      "data/popup-c.html": scriptPage("popup-c.js"),
      "data/popup-c.js": function () {
        // Close the popup before the document is fully-loaded to make sure that
        // we handle this case sanely.
        browser.test.sendMessage("from-popup", "popup-c");
        window.close();
      },
    },
  });

  await Promise.all([
    extension.startup(),
    extension.awaitMessage("background-page-ready"),
  ]);

  let widget = getBrowserActionWidget(extension);

  // Move the browserAction widget to the area targeted by this test.
  CustomizableUI.addWidgetToArea(widget.id, area);

  async function setBrowserActionPopup(opts) {
    extension.sendMessage({ type: "setBrowserActionPopup", ...opts });
    await extension.awaitMessage("setBrowserActionPopup:done");
  }

  async function setShowPopupOnMiddleClick(show) {
    extension.sendMessage({ type: "setMiddleClickShowPopup", show });
    await extension.awaitMessage("setMiddleClickShowPopup:done");
  }

  async function runTest({
    actionType,
    waitForPopupLoaded,
    expectPopup,
    expectOnClicked,
    closePopup,
  }) {
    const oncePopupPageClosed = promiseExtensionPageClosed(extension, "popup");
    const oncePopupLoaded = waitForPopupLoaded
      ? awaitExtensionPanel(extension)
      : undefined;

    if (actionType === "click") {
      clickBrowserAction(extension);
    } else if (actionType === "trigger") {
      getBrowserAction(extension).triggerAction(window);
    } else if (actionType === "middleClick") {
      clickBrowserAction(extension, window, { button: 1 });
    }

    if (expectOnClicked) {
      await extension.awaitMessage("browserAction-onClicked");
    }

    if (expectPopup) {
      info(`Waiting for popup: ${expectPopup}`);
      is(
        await extension.awaitMessage("from-popup"),
        expectPopup,
        "expected popup opened"
      );
    }

    await oncePopupLoaded;

    if (closePopup) {
      info("Closing popup");
      await closeBrowserAction(extension);
      await assertViewCount(extension, 1, oncePopupPageClosed);
    }

    return { oncePopupPageClosed };
  }

  // Run the sequence of test cases.
  const tests = [
    async () => {
      info(`Click browser action, expect popup "a".`);

      await runTest({
        actionType: "click",
        expectPopup: "popup-a",
        closePopup: true,
      });
    },
    async () => {
      info(`Click browser action again, expect popup "a".`);

      await runTest({
        actionType: "click",
        expectPopup: "popup-a",
        waitForPopupLoaded: true,
        closePopup: true,
      });
    },
    async () => {
      info(`Call triggerAction, expect popup "a" again. Leave popup open.`);

      const { oncePopupPageClosed } = await runTest({
        actionType: "trigger",
        expectPopup: "popup-a",
        waitForPopupLoaded: true,
      });

      await assertViewCount(extension, 2);

      info(`Call triggerAction again. Expect remaining popup closed.`);
      getBrowserAction(extension).triggerAction(window);

      await assertViewCount(extension, 1, oncePopupPageClosed);
    },
    async () => {
      info(`Call triggerAction again. Expect popup "a" again.`);

      await runTest({
        actionType: "trigger",
        expectPopup: "popup-a",
        closePopup: true,
      });
    },
    async () => {
      info(`Set popup to "c" and click browser action. Expect popup "c".`);

      await setBrowserActionPopup({ popup: "data/popup-c.html" });

      const { oncePopupPageClosed } = await runTest({
        actionType: "click",
        expectPopup: "popup-c",
      });

      await assertViewCount(extension, 1, oncePopupPageClosed);
    },
    async () => {
      info(`Set popup to "b" and click browser action. Expect popup "b".`);

      await setBrowserActionPopup({ popup: "data/popup-b.html" });

      await runTest({
        actionType: "click",
        expectPopup: "popup-b",
        closePopup: true,
      });
    },
    async () => {
      info(`Click browser action again, expect popup "b".`);

      await runTest({
        actionType: "click",
        expectPopup: "popup-b",
        closePopup: true,
      });
    },
    async () => {
      info(`Middle-click browser action, expect an event only.`);

      await setShowPopupOnMiddleClick(false);

      await runTest({
        actionType: "middleClick",
        expectOnClicked: true,
      });
    },
    async () => {
      info(
        `Middle-click browser action again, expect a click event then a popup.`
      );

      await setShowPopupOnMiddleClick(true);

      await runTest({
        actionType: "middleClick",
        expectOnClicked: true,
        expectPopup: "popup-b",
        closePopup: true,
      });
    },
    async () => {
      info(`Clear popup URL. Click browser action. Expect click event.`);

      await setBrowserActionPopup({ popup: "" });

      await runTest({
        actionType: "click",
        expectOnClicked: true,
      });
    },
    async () => {
      info(`Click browser action again. Expect another click event.`);

      await runTest({
        actionType: "click",
        expectOnClicked: true,
      });
    },
    async () => {
      info(`Call triggerAction. Expect click event.`);

      await runTest({
        actionType: "trigger",
        expectOnClicked: true,
      });
    },
    async () => {
      info(
        `Set window-specific popup to "b" and click browser action. Expect popup "b".`
      );

      await setBrowserActionPopup({
        popup: "data/popup-b.html",
        onCurrentWindowId: true,
      });

      await runTest({
        actionType: "click",
        expectPopup: "popup-b",
        closePopup: true,
      });
    },
    async () => {
      info(
        `Set tab-specific popup to "a" and click browser action. Expect popup "a", and leave open.`
      );

      await setBrowserActionPopup({
        popup: "/popup-a.html",
        onActiveTabId: true,
      });

      const { oncePopupPageClosed } = await runTest({
        actionType: "click",
        expectPopup: "popup-a",
      });
      assertViewCount(extension, 2);

      info(`Tell popup "a" to call window.close(). Expect popup closed.`);
      extension.sendMessage("close-popup-using-window.close");

      await assertViewCount(extension, 1, oncePopupPageClosed);
    },
  ];

  for (let test of tests) {
    await test();
  }

  // Unload the extension and verify that the browserAction widget is gone.
  await extension.unload();

  let view = document.getElementById(widget.viewId);
  is(view, null, "browserAction view removed from document");
}

add_task(async function testBrowserActionInToolbar() {
  await testInArea(CustomizableUI.AREA_NAVBAR);
});

add_task(async function testBrowserActionInPanel() {
  await testInArea(getCustomizableUIPanelID());
});
