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

add_task(async function tabsSendMessageReply() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs"],

      content_scripts: [
        {
          matches: ["http://example.com/"],
          js: ["content-script.js"],
          run_at: "document_start",
        },
      ],
    },

    background: async function () {
      let firstTab;
      let promiseResponse = new Promise(resolve => {
        browser.runtime.onMessage.addListener((msg, sender, respond) => {
          if (msg == "content-script-ready") {
            let tabId = sender.tab.id;

            Promise.all([
              promiseResponse,

              browser.tabs.sendMessage(tabId, "respond-now"),
              browser.tabs.sendMessage(tabId, "respond-now-2"),
              new Promise(resolve =>
                browser.tabs.sendMessage(tabId, "respond-soon", resolve)
              ),
              browser.tabs.sendMessage(tabId, "respond-promise"),
              browser.tabs.sendMessage(tabId, "respond-promise-false"),
              browser.tabs.sendMessage(tabId, "respond-false"),
              browser.tabs.sendMessage(tabId, "respond-never"),
              new Promise(resolve => {
                browser.runtime.sendMessage("respond-never", response => {
                  resolve(response);
                });
              }),

              browser.tabs
                .sendMessage(tabId, "respond-error")
                .catch(error => Promise.resolve({ error })),
              browser.tabs
                .sendMessage(tabId, "throw-error")
                .catch(error => Promise.resolve({ error })),

              browser.tabs
                .sendMessage(tabId, "respond-uncloneable")
                .catch(error => Promise.resolve({ error })),
              browser.tabs
                .sendMessage(tabId, "reject-uncloneable")
                .catch(error => Promise.resolve({ error })),
              browser.tabs
                .sendMessage(tabId, "reject-undefined")
                .catch(error => Promise.resolve({ error })),
              browser.tabs
                .sendMessage(tabId, "throw-undefined")
                .catch(error => Promise.resolve({ error })),

              browser.tabs
                .sendMessage(firstTab, "no-listener")
                .catch(error => Promise.resolve({ error })),
            ])
              .then(
                ([
                  response,
                  respondNow,
                  respondNow2,
                  respondSoon,
                  respondPromise,
                  respondPromiseFalse,
                  respondFalse,
                  respondNever,
                  respondNever2,
                  respondError,
                  throwError,
                  respondUncloneable,
                  rejectUncloneable,
                  rejectUndefined,
                  throwUndefined,
                  noListener,
                ]) => {
                  browser.test.assertEq(
                    "expected-response",
                    response,
                    "Content script got the expected response"
                  );

                  browser.test.assertEq(
                    "respond-now",
                    respondNow,
                    "Got the expected immediate response"
                  );
                  browser.test.assertEq(
                    "respond-now-2",
                    respondNow2,
                    "Got the expected immediate response from the second listener"
                  );
                  browser.test.assertEq(
                    "respond-soon",
                    respondSoon,
                    "Got the expected delayed response"
                  );
                  browser.test.assertEq(
                    "respond-promise",
                    respondPromise,
                    "Got the expected promise response"
                  );
                  browser.test.assertEq(
                    false,
                    respondPromiseFalse,
                    "Got the expected false value as a promise result"
                  );
                  browser.test.assertEq(
                    undefined,
                    respondFalse,
                    "Got the expected no-response when onMessage returns false"
                  );
                  browser.test.assertEq(
                    undefined,
                    respondNever,
                    "Got the expected no-response resolution"
                  );
                  browser.test.assertEq(
                    undefined,
                    respondNever2,
                    "Got the expected no-response resolution"
                  );

                  browser.test.assertEq(
                    "respond-error",
                    respondError.error.message,
                    "Got the expected error response"
                  );
                  browser.test.assertEq(
                    "throw-error",
                    throwError.error.message,
                    "Got the expected thrown error response"
                  );

                  browser.test.assertEq(
                    "Could not establish connection. Receiving end does not exist.",
                    respondUncloneable.error.message,
                    "An uncloneable response should be ignored"
                  );
                  browser.test.assertEq(
                    "An unexpected error occurred",
                    rejectUncloneable.error.message,
                    "Got the expected error for a rejection with an uncloneable value"
                  );
                  browser.test.assertEq(
                    "An unexpected error occurred",
                    rejectUndefined.error.message,
                    "Got the expected error for a void rejection"
                  );
                  browser.test.assertEq(
                    "An unexpected error occurred",
                    throwUndefined.error.message,
                    "Got the expected error for a void throw"
                  );

                  browser.test.assertEq(
                    "Could not establish connection. Receiving end does not exist.",
                    noListener.error.message,
                    "Got the expected no listener response"
                  );

                  return browser.tabs.remove(tabId);
                }
              )
              .then(() => {
                browser.test.notifyPass("sendMessage");
              });

            return Promise.resolve("expected-response");
          } else if (msg[0] == "got-response") {
            resolve(msg[1]);
          }
        });
      });

      let tabs = await browser.tabs.query({
        currentWindow: true,
        active: true,
      });
      firstTab = tabs[0].id;
      browser.tabs.create({ url: "http://example.com/" });
    },

    files: {
      "content-script.js": async function () {
        browser.runtime.onMessage.addListener((msg, sender, respond) => {
          if (msg == "respond-now") {
            respond(msg);
          } else if (msg == "respond-soon") {
            setTimeout(() => {
              respond(msg);
            }, 0);
            return true;
          } else if (msg == "respond-promise") {
            return Promise.resolve(msg);
          } else if (msg == "respond-promise-false") {
            return Promise.resolve(false);
          } else if (msg == "respond-false") {
            // return false means that respond() is not expected to be called.
            setTimeout(() => respond("should be ignored"));
            return false;
          } else if (msg == "respond-never") {
            return undefined;
          } else if (msg == "respond-error") {
            return Promise.reject(new Error(msg));
          } else if (msg === "respond-uncloneable") {
            return Promise.resolve(window);
          } else if (msg === "reject-uncloneable") {
            return Promise.reject(window);
          } else if (msg == "reject-undefined") {
            return Promise.reject();
          } else if (msg == "throw-undefined") {
            throw undefined; // eslint-disable-line no-throw-literal
          } else if (msg == "throw-error") {
            throw new Error(msg);
          }
        });

        browser.runtime.onMessage.addListener((msg, sender, respond) => {
          if (msg == "respond-now") {
            respond("hello");
          } else if (msg == "respond-now-2") {
            respond(msg);
          }
        });

        let response = await browser.runtime.sendMessage(
          "content-script-ready"
        );
        browser.runtime.sendMessage(["got-response", response]);
      },
    },
  });

  await extension.startup();

  await extension.awaitFinish("sendMessage");

  await extension.unload();
});

add_task(async function tabsSendHidden() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs"],

      content_scripts: [
        {
          matches: ["http://example.com/content*"],
          js: ["content-script.js"],
          run_at: "document_start",
        },
      ],
    },

    background: async function () {
      let resolveContent;
      browser.runtime.onMessage.addListener((msg, sender) => {
        if (msg[0] == "content-ready") {
          resolveContent(msg[1]);
        }
      });

      let awaitContent = url => {
        return new Promise(resolve => {
          resolveContent = resolve;
        }).then(result => {
          browser.test.assertEq(url, result, "Expected content script URL");
        });
      };

      try {
        const URL1 = "http://example.com/content1.html";
        const URL2 = "http://example.com/content2.html";

        let tab = await browser.tabs.create({ url: URL1 });
        await awaitContent(URL1);

        let url = await browser.tabs.sendMessage(tab.id, URL1);
        browser.test.assertEq(
          URL1,
          url,
          "Should get response from expected content window"
        );

        await browser.tabs.update(tab.id, { url: URL2 });
        await awaitContent(URL2);

        url = await browser.tabs.sendMessage(tab.id, URL2);
        browser.test.assertEq(
          URL2,
          url,
          "Should get response from expected content window"
        );

        // Repeat once just to be sure the first message was processed by all
        // listeners before we exit the test.
        url = await browser.tabs.sendMessage(tab.id, URL2);
        browser.test.assertEq(
          URL2,
          url,
          "Should get response from expected content window"
        );

        await browser.tabs.remove(tab.id);

        browser.test.notifyPass("contentscript-bfcache-window");
      } catch (error) {
        browser.test.fail(`Error: ${error} :: ${error.stack}`);
        browser.test.notifyFail("contentscript-bfcache-window");
      }
    },

    files: {
      "content-script.js": function () {
        // Store this in a local variable to make sure we don't touch any
        // properties of the possibly-hidden content window.
        let href = window.location.href;

        browser.runtime.onMessage.addListener((msg, sender) => {
          browser.test.assertEq(
            href,
            msg,
            "Should be in the expected content window"
          );

          return Promise.resolve(href);
        });

        browser.runtime.sendMessage(["content-ready", href]);
      },
    },
  });

  await extension.startup();

  await extension.awaitFinish("contentscript-bfcache-window");

  await extension.unload();
});

add_task(async function tabsSendMessageNoExceptionOnNonExistentTab() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs"],
    },

    async background() {
      let url =
        "http://example.com/mochitest/browser/browser/components/extensions/test/browser/file_dummy.html";
      let tab = await browser.tabs.create({ url });

      await browser.test.assertRejects(
        browser.tabs.sendMessage(tab.id, "message"),
        /Could not establish connection. Receiving end does not exist./,
        "exception should be raised on tabs.sendMessage to nonexistent tab"
      );

      await browser.test.assertRejects(
        browser.tabs.sendMessage(tab.id + 100, "message"),
        /Could not establish connection. Receiving end does not exist./,
        "exception should be raised on tabs.sendMessage to nonexistent tab"
      );

      await browser.tabs.remove(tab.id);

      browser.test.notifyPass("tabs.sendMessage");
    },
  });

  await extension.startup();
  await extension.awaitFinish("tabs.sendMessage");

  await extension.unload();
});
