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

add_task(async function no_cookies_permission() {
  await SpecialPowers.pushPrefEnv({"set": [
    ["privacy.userContext.enabled", true],
  ]});

  let extension = ExtensionTestUtils.loadExtension({
    async background() {
      await browser.test.assertRejects(
        browser.windows.create({cookieStoreId: "firefox-container-1"}),
        /No permission for cookieStoreId/,
        "cookieStoreId requires cookies permission");
      browser.test.sendMessage("done");
    },
  });

  await extension.startup();
  await extension.awaitMessage("done");
  await extension.unload();
});

add_task(async function invalid_cookieStoreId() {
  await SpecialPowers.pushPrefEnv({"set": [
    ["privacy.userContext.enabled", true],
  ]});

  let extension = ExtensionTestUtils.loadExtension({
    incognitoOverride: "spanning",
    manifest: {
      permissions: ["cookies"],
    },
    async background() {
      await browser.test.assertRejects(
        browser.windows.create({cookieStoreId: "not-firefox-container-1"}),
        /Illegal cookieStoreId/,
        "cookieStoreId must be valid");

      await browser.test.assertRejects(
        browser.windows.create({cookieStoreId: "firefox-private"}),
        /Illegal to set private cookieStoreId in a non-private window/,
        "cookieStoreId cannot be private in a non-private window");
      await browser.test.assertRejects(
        browser.windows.create({cookieStoreId: "firefox-default", incognito: true}),
        /Illegal to set non-private cookieStoreId in a private window/,
        "cookieStoreId cannot be non-private in an private window");

      await browser.test.assertRejects(
        browser.windows.create({cookieStoreId: "firefox-container-1", incognito: true}),
        /Illegal to set non-private cookieStoreId in a private window/,
        "cookieStoreId cannot be a container tab ID in a private window");

      browser.test.sendMessage("done");
    },
  });

  await extension.startup();
  await extension.awaitMessage("done");
  await extension.unload();
});

add_task(async function perma_private_browsing_mode() {
  await SpecialPowers.pushPrefEnv({set: [["browser.privatebrowsing.autostart", true]]});

  let extension = ExtensionTestUtils.loadExtension({
    incognitoOverride: "spanning",
    manifest: {
      "permissions": ["tabs", "cookies"],
    },
    async background() {
      await browser.test.assertRejects(
        browser.windows.create({cookieStoreId: "firefox-container-1"}),
        /Contextual identities are unavailable in permanent private browsing mode/,
        "cookieStoreId cannot be a container tab ID in perma-private browsing mode");

      browser.test.sendMessage("done");
    },
  });
  await extension.startup();
  await extension.awaitMessage("done");
  await extension.unload();
  await SpecialPowers.popPrefEnv();
});

add_task(async function userContext_disabled() {
  await SpecialPowers.pushPrefEnv({"set": [["privacy.userContext.enabled", false]]});
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      "permissions": ["tabs", "cookies"],
    },
    async background() {
      await browser.test.assertRejects(
        browser.windows.create({cookieStoreId: "firefox-container-1"}),
        /Contextual identities are currently disabled/,
        "cookieStoreId cannot be a container tab ID when contextual identities are disabled");
      browser.test.sendMessage("done");
    },
  });
  await extension.startup();
  await extension.awaitMessage("done");
  await extension.unload();
  await SpecialPowers.popPrefEnv();
});

add_task(async function valid_cookieStoreId() {
  await SpecialPowers.pushPrefEnv({"set": [
    ["privacy.userContext.enabled", true],
  ]});

  const testCases = [{
    description: "no explicit URL",
    createParams: {
      cookieStoreId: "firefox-container-1",
    },
    expectedCookieStoreIds: [
      "firefox-container-1",
    ],
    expectedExecuteScriptResult: [
      // Default URL is about:home, and extensions cannot run scripts in it.
      "Missing host permission for the tab",
    ],
  }, {
    description: "one URL",
    createParams: {
      url: "about:blank",
      cookieStoreId: "firefox-container-1",
    },
    expectedCookieStoreIds: [
      "firefox-container-1",
    ],
    expectedExecuteScriptResult: [
      "about:blank - null",
    ],
  }, {
    description: "one URL in an array",
    createParams: {
      url: ["about:blank"],
      cookieStoreId: "firefox-container-1",
    },
    expectedCookieStoreIds: [
      "firefox-container-1",
    ],
    expectedExecuteScriptResult: [
      "about:blank - null",
    ],
  }, {
    description: "two URLs in an array",
    createParams: {
      url: ["about:blank", "about:blank"],
      cookieStoreId: "firefox-container-1",
    },
    expectedCookieStoreIds: [
      "firefox-container-1",
      "firefox-container-1",
    ],
    expectedExecuteScriptResult: [
      "about:blank - null",
      "about:blank - null",
    ],
  }];

  async function background(testCases) {
    let readyTabs = new Map();
    let tabReadyCheckers = new Set();
    browser.webNavigation.onCompleted.addListener(({url, tabId, frameId}) => {
      if (frameId === 0) {
        readyTabs.set(tabId, url);
        browser.test.log(`Detected navigation in tab ${tabId} to ${url}.`);

        for (let check of tabReadyCheckers) {
          check(tabId, url);
        }
      }
    });
    async function awaitTabReady(tabId, expectedUrl) {
      if (readyTabs.get(tabId) === expectedUrl) {
        browser.test.log(`Tab ${tabId} was ready with URL ${expectedUrl}.`);
        return;
      }
      await new Promise(resolve => {
        browser.test.log(`Waiting for tab ${tabId} to load URL ${expectedUrl}...`);
        tabReadyCheckers.add(function check(completedTabId, completedUrl) {
          if (completedTabId === tabId && completedUrl === expectedUrl) {
            tabReadyCheckers.delete(check);
            resolve();
          }
        });
      });
      browser.test.log(`Tab ${tabId} is ready with URL ${expectedUrl}.`);
    }

    async function executeScriptAndGetResult(tabId) {
      try {
        return (await browser.tabs.executeScript(tabId, {
          matchAboutBlank: true,
          code: "`${document.URL} - ${origin}`",
        }))[0];
      } catch (e) {
        return e.message;
      }
    }
    for (let {description, createParams, expectedCookieStoreIds, expectedExecuteScriptResult} of testCases) {
      let win = await browser.windows.create(createParams);

      browser.test.assertEq(expectedCookieStoreIds.length, win.tabs.length, "Expected number of tabs");

      for (let [i, expectedCookieStoreId] of Object.entries(expectedCookieStoreIds)) {
        browser.test.assertEq(expectedCookieStoreId, win.tabs[i].cookieStoreId, `expected cookieStoreId for tab ${i} (${description})`);
      }

      for (let [i, expectedResult] of Object.entries(expectedExecuteScriptResult)) {
        // Wait until the the tab can process the tabs.executeScript calls.
        // TODO: Remove this when bug 1418655 and bug 1397667 are fixed.
        let expectedUrl = Array.isArray(createParams.url) ? createParams.url[i] : createParams.url || "about:home";
        await awaitTabReady(win.tabs[i].id, expectedUrl);

        let result = await executeScriptAndGetResult(win.tabs[i].id);
        browser.test.assertEq(expectedResult, result, `expected executeScript result for tab ${i} (${description})`);
      }

      await browser.windows.remove(win.id);
    }
    browser.test.sendMessage("done");
  }
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["cookies", "webNavigation"],
    },
    background: `(${background})(${JSON.stringify(testCases)})`,
  });

  await extension.startup();
  await extension.awaitMessage("done");
  await extension.unload();
});

add_task(async function cookieStoreId_and_tabId() {
  await SpecialPowers.pushPrefEnv({"set": [
    ["privacy.userContext.enabled", true],
  ]});

  let extension = ExtensionTestUtils.loadExtension({
    incognitoOverride: "spanning",
    manifest: {
      permissions: ["cookies"],
    },
    async background() {
      for (let cookieStoreId of ["firefox-default", "firefox-container-1"]) {
        let {id: normalTabId} = await browser.tabs.create({cookieStoreId});

        await browser.test.assertRejects(
          browser.windows.create({cookieStoreId: "firefox-private", tabId: normalTabId}),
          /`cookieStoreId` must match the tab's cookieStoreId/,
          "Cannot use cookieStoreId for pre-existing tabs with a different cookieStoreId");

        let win = await browser.windows.create({cookieStoreId, tabId: normalTabId});
        browser.test.assertEq(cookieStoreId, win.tabs[0].cookieStoreId, "Adopted tab");
        await browser.windows.remove(win.id);
      }

      {
        let privateWindow = await browser.windows.create({incognito: true});
        let privateTabId = privateWindow.tabs[0].id;

        await browser.test.assertRejects(
          browser.windows.create({cookieStoreId: "firefox-default", tabId: privateTabId}),
          /`cookieStoreId` must match the tab's cookieStoreId/,
          "Cannot use cookieStoreId for pre-existing tab in a private window");
        let win = await browser.windows.create({cookieStoreId: "firefox-private", tabId: privateTabId});
        browser.test.assertEq("firefox-private", win.tabs[0].cookieStoreId, "Adopted private tab");
        await browser.windows.remove(win.id);

        await browser.test.assertRejects(
          browser.windows.remove(privateWindow.id),
          /Invalid window ID:/,
          "The original private window should have been closed when its only tab was adopted.");
      }

      browser.test.sendMessage("done");
    },
  });

  await extension.startup();
  await extension.awaitMessage("done");
  await extension.unload();
});
