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

const triggeringPrincipal_base64 = E10SUtils.SERIALIZED_SYSTEMPRINCIPAL;

let lazyTabState = {
  entries: [
    {
      url: "http://example.com/",
      triggeringPrincipal_base64,
      title: "Example Domain",
    },
  ],
};

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

    background() {
      browser.webNavigation.onCompleted.addListener(
        async details => {
          browser.test.log(`webNav onCompleted received for ${details.tabId}`);
          let updatedTab = await browser.tabs.get(details.tabId);
          browser.test.assertEq(
            false,
            updatedTab.discarded,
            "lazy to non-lazy update discard property"
          );
          browser.test.notifyPass("test-finished");
        },
        { url: [{ hostContains: "example.com" }] }
      );

      browser.tabs.onCreated.addListener(function (tab) {
        browser.test.assertEq(
          true,
          tab.discarded,
          "non-lazy tab onCreated discard property"
        );
        browser.tabs.update(tab.id, { active: true });
      });
    },
  });

  await extension.startup();

  let testTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
    createLazyBrowser: true,
  });
  SessionStore.setTabState(testTab, lazyTabState);

  await extension.awaitFinish("test-finished");
  await extension.unload();

  BrowserTestUtils.removeTab(testTab);
});

// Regression test for Bug 1819794.
add_task(async function test_create_discarded_with_cookieStoreId() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["contextualIdentities", "cookies"],
    },
    async background() {
      const [{ cookieStoreId }] = await browser.contextualIdentities.query({});
      browser.test.assertEq(
        "firefox-container-1",
        cookieStoreId,
        "Got expected cookieStoreId"
      );
      await browser.tabs.create({
        url: `http://example.com/#${cookieStoreId}`,
        cookieStoreId,
        discarded: true,
      });
      await browser.tabs.create({
        url: `http://example.com/#no-container`,
        discarded: true,
      });
    },
    // Needed by ExtensionSettingsStore (as a side-effect of contextualIdentities permission).
    useAddonManager: "temporary",
  });

  const tabContainerPromise = BrowserTestUtils.waitForEvent(
    window,
    "TabOpen",
    false,
    evt => {
      return evt.target.getAttribute("usercontextid", "1");
    }
  ).then(evt => evt.target);
  const tabDefaultPromise = BrowserTestUtils.waitForEvent(
    window,
    "TabOpen",
    false,
    evt => {
      return !evt.target.hasAttribute("usercontextid");
    }
  ).then(evt => evt.target);

  await extension.startup();

  const tabContainer = await tabContainerPromise;
  ok(
    tabContainer.hasAttribute("pending"),
    "new container tab should be discarded"
  );
  const tabContainerState = SessionStore.getTabState(tabContainer);
  is(
    JSON.parse(tabContainerState).userContextId,
    1,
    `Expect a userContextId associated to the new discarded container tab: ${tabContainerState}`
  );

  const tabDefault = await tabDefaultPromise;
  ok(
    tabDefault.hasAttribute("pending"),
    "new non-container tab should be discarded"
  );
  const tabDefaultState = SessionStore.getTabState(tabDefault);
  is(
    JSON.parse(tabDefaultState).userContextId,
    0,
    `Expect userContextId 0 associated to the new discarded non-container tab: ${tabDefaultState}`
  );

  BrowserTestUtils.removeTab(tabContainer);
  BrowserTestUtils.removeTab(tabDefault);
  await extension.unload();
});

// If discard is called immediately after creating a new tab, the new tab may not have loaded,
// and the sessionstore for that tab is not ready for discarding.  The result was a corrupted
// sessionstore for the tab, which when the tab was activated, resulted in a tab with partial
// state.
add_task(async function test_create_then_discard() {
  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["tabs", "webNavigation"],
    },

    background: async function () {
      let createdTab;

      browser.tabs.onUpdated.addListener((tabId, updatedInfo) => {
        if (!updatedInfo.discarded) {
          return;
        }

        browser.webNavigation.onCompleted.addListener(
          async details => {
            browser.test.assertEq(
              createdTab.id,
              details.tabId,
              "created tab navigation is completed"
            );
            let activeTab = await browser.tabs.get(details.tabId);
            browser.test.assertEq(
              "http://example.com/",
              details.url,
              "created tab url is correct"
            );
            browser.test.assertEq(
              "http://example.com/",
              activeTab.url,
              "created tab url is correct"
            );
            browser.tabs.remove(details.tabId);
            browser.test.notifyPass("test-finished");
          },
          { url: [{ hostContains: "example.com" }] }
        );

        browser.tabs.update(tabId, { active: true });
      });

      createdTab = await browser.tabs.create({
        url: "http://example.com/",
        active: false,
      });
      browser.tabs.discard(createdTab.id);
    },
  });
  await extension.startup();
  await extension.awaitFinish("test-finished");
  await extension.unload();
});

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

    background() {
      let tabOpts = {
        url: "http://example.com/",
        active: false,
        discarded: true,
        title: "discarded tab",
      };

      browser.webNavigation.onCompleted.addListener(
        async details => {
          let activeTab = await browser.tabs.get(details.tabId);
          browser.test.assertEq(
            tabOpts.url,
            activeTab.url,
            "restored tab url matches active tab url"
          );
          browser.test.assertEq(
            "mochitest index /",
            activeTab.title,
            "restored tab title is correct"
          );
          browser.tabs.remove(details.tabId);
          browser.test.notifyPass("test-finished");
        },
        { url: [{ hostContains: "example.com" }] }
      );

      browser.tabs.onCreated.addListener(tab => {
        browser.test.assertEq(
          tabOpts.active,
          tab.active,
          "lazy tab is not active"
        );
        browser.test.assertEq(
          tabOpts.discarded,
          tab.discarded,
          "lazy tab is discarded"
        );
        browser.test.assertEq(tabOpts.url, tab.url, "lazy tab url is correct");
        browser.test.assertEq(
          tabOpts.title,
          tab.title,
          "lazy tab title is correct"
        );
        browser.tabs.update(tab.id, { active: true });
      });

      browser.tabs.create(tabOpts);
    },
  });
  await extension.startup();
  await extension.awaitFinish("test-finished");
  await extension.unload();
});

add_task(async function test_discarded_private_tab_restored() {
  let extension = ExtensionTestUtils.loadExtension({
    incognitoOverride: "spanning",

    background() {
      let isDiscarding = false;
      browser.tabs.onUpdated.addListener(
        async function listener(tabId, changeInfo, tab) {
          const { active, discarded, incognito } = tab;
          if (!incognito || active || discarded || isDiscarding) {
            return;
          }
          // Remove the onUpdated listener to prevent intermittent failure
          // to be hit if the listener gets called again for unrelated
          // tabs.onUpdated events that may get fired after the test case got
          // the tab-discarded test message that was expecting.
          isDiscarding = true;
          browser.tabs.onUpdated.removeListener(listener);
          browser.test.log(
            `Test extension discarding ${tabId}: ${JSON.stringify(changeInfo)}`
          );
          await browser.tabs.discard(tabId);
          browser.test.sendMessage("tab-discarded");
        },
        { properties: ["status"] }
      );
    },
  });

  // Open a private browsing window.
  const privateWin = await BrowserTestUtils.openNewBrowserWindow({
    private: true,
  });

  await extension.startup();

  const newTab = await BrowserTestUtils.addTab(
    privateWin.gBrowser,
    "https://example.com/"
  );
  await extension.awaitMessage("tab-discarded");
  is(newTab.getAttribute("pending"), "true", "private tab should be discarded");

  const promiseTabLoaded = BrowserTestUtils.browserLoaded(newTab.linkedBrowser);

  info("Switching to the discarded background tab");
  await BrowserTestUtils.switchTab(privateWin.gBrowser, newTab);

  info("Wait for the restored tab to complete loading");
  await promiseTabLoaded;
  is(
    newTab.hasAttribute("pending"),
    false,
    "discarded private tab should have been restored"
  );

  is(
    newTab.linkedBrowser.currentURI.spec,
    "https://example.com/",
    "Got the expected url on the restored tab"
  );

  await extension.unload();
  await BrowserTestUtils.closeWindow(privateWin);
});

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

    background() {
      browser.test.onMessage.addListener(async msg => {
        let [tab] = await browser.tabs.query({ url: "http://example.com/" });
        if (msg == "update") {
          await browser.tabs.update(tab.id, { url: "https://example.com/" });
        } else {
          browser.test.fail(`Unexpected message received: ${msg}`);
        }
      });
      browser.test.sendMessage("ready");
    },
  });

  await extension.startup();
  await extension.awaitMessage("ready");

  let lazyTab = BrowserTestUtils.addTab(gBrowser, "http://example.com/", {
    createLazyBrowser: true,
    lazyTabTitle: "Example Domain",
  });

  let tabBrowserInsertedPromise = BrowserTestUtils.waitForEvent(
    lazyTab,
    "TabBrowserInserted"
  );

  SimpleTest.waitForExplicitFinish();
  let waitForConsole = new Promise(resolve => {
    SimpleTest.monitorConsole(resolve, [
      {
        message:
          /Lazy browser prematurely inserted via 'loadURI' property access:/,
        forbid: true,
      },
    ]);
  });

  extension.sendMessage("update");
  await tabBrowserInsertedPromise;

  await BrowserTestUtils.waitForBrowserStateChange(
    lazyTab.linkedBrowser,
    "https://example.com/",
    stateFlags => {
      return (
        stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
        stateFlags & Ci.nsIWebProgressListener.STATE_STOP
      );
    }
  );

  await TestUtils.waitForTick();
  BrowserTestUtils.removeTab(lazyTab);

  await extension.unload();

  SimpleTest.endMonitorConsole();
  await waitForConsole;
});
