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

ChromeUtils.defineESModuleGetters(this, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  Management: "resource://gre/modules/Extension.sys.mjs",
});

const { AboutNewTab } = ChromeUtils.import(
  "resource:///modules/AboutNewTab.jsm"
);

const { AddonTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/AddonTestUtils.sys.mjs"
);

const {
  createAppInfo,
  promiseRestartManager,
  promiseShutdownManager,
  promiseStartupManager,
} = AddonTestUtils;

AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();

createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42");

function awaitEvent(eventName) {
  return new Promise(resolve => {
    Management.once(eventName, (e, ...args) => resolve(...args));
  });
}

const DEFAULT_NEW_TAB_URL = AboutNewTab.newTabURL;

add_task(async function test_multiple_extensions_overriding_newtab_page() {
  const NEWTAB_URI_2 = "webext-newtab-1.html";
  const NEWTAB_URI_3 = "webext-newtab-2.html";
  const EXT_2_ID = "ext2@tests.mozilla.org";
  const EXT_3_ID = "ext3@tests.mozilla.org";

  const CONTROLLED_BY_THIS = "controlled_by_this_extension";
  const CONTROLLED_BY_OTHER = "controlled_by_other_extensions";
  const NOT_CONTROLLABLE = "not_controllable";

  const NEW_TAB_PRIVATE_ALLOWED = "browser.newtab.privateAllowed";
  const NEW_TAB_EXTENSION_CONTROLLED = "browser.newtab.extensionControlled";

  function background() {
    browser.test.onMessage.addListener(async msg => {
      switch (msg) {
        case "checkNewTabPage":
          let newTabPage = await browser.browserSettings.newTabPageOverride.get(
            {}
          );
          browser.test.sendMessage("newTabPage", newTabPage);
          break;
        case "trySet":
          let setResult = await browser.browserSettings.newTabPageOverride.set({
            value: "foo",
          });
          browser.test.assertFalse(
            setResult,
            "Calling newTabPageOverride.set returns false."
          );
          browser.test.sendMessage("newTabPageSet");
          break;
        case "tryClear":
          let clearResult =
            await browser.browserSettings.newTabPageOverride.clear({});
          browser.test.assertFalse(
            clearResult,
            "Calling newTabPageOverride.clear returns false."
          );
          browser.test.sendMessage("newTabPageCleared");
          break;
      }
    });
  }

  async function checkNewTabPageOverride(
    ext,
    expectedValue,
    expectedLevelOfControl
  ) {
    ext.sendMessage("checkNewTabPage");
    let newTabPage = await ext.awaitMessage("newTabPage");

    ok(
      newTabPage.value.endsWith(expectedValue),
      `newTabPageOverride setting returns the expected value ending with: ${expectedValue}.`
    );
    equal(
      newTabPage.levelOfControl,
      expectedLevelOfControl,
      `newTabPageOverride setting returns the expected levelOfControl: ${expectedLevelOfControl}.`
    );
  }

  function verifyNewTabSettings(ext, expectedLevelOfControl) {
    if (expectedLevelOfControl !== NOT_CONTROLLABLE) {
      // Verify the preferences are set as expected.
      let policy = WebExtensionPolicy.getByID(ext.id);
      equal(
        policy && policy.privateBrowsingAllowed,
        Services.prefs.getBoolPref(NEW_TAB_PRIVATE_ALLOWED),
        "private browsing flag set correctly"
      );
      ok(
        Services.prefs.getBoolPref(NEW_TAB_EXTENSION_CONTROLLED),
        `extension controlled flag set correctly`
      );
    } else {
      ok(
        !Services.prefs.prefHasUserValue(NEW_TAB_PRIVATE_ALLOWED),
        "controlled flag reset"
      );
      ok(
        !Services.prefs.prefHasUserValue(NEW_TAB_EXTENSION_CONTROLLED),
        "controlled flag reset"
      );
    }
  }

  let extObj = {
    manifest: {
      chrome_url_overrides: {},
      permissions: ["browserSettings"],
    },
    useAddonManager: "temporary",
    background,
  };

  let ext1 = ExtensionTestUtils.loadExtension(extObj);

  extObj.manifest.chrome_url_overrides = { newtab: NEWTAB_URI_2 };
  extObj.manifest.browser_specific_settings = { gecko: { id: EXT_2_ID } };
  let ext2 = ExtensionTestUtils.loadExtension(extObj);

  extObj.manifest.chrome_url_overrides = { newtab: NEWTAB_URI_3 };
  extObj.manifest.browser_specific_settings.gecko.id = EXT_3_ID;
  extObj.incognitoOverride = "spanning";
  let ext3 = ExtensionTestUtils.loadExtension(extObj);

  equal(
    AboutNewTab.newTabURL,
    DEFAULT_NEW_TAB_URL,
    "newTabURL is set to the default."
  );

  await promiseStartupManager();

  await ext1.startup();
  equal(
    AboutNewTab.newTabURL,
    DEFAULT_NEW_TAB_URL,
    "newTabURL is still set to the default."
  );

  await checkNewTabPageOverride(ext1, AboutNewTab.newTabURL, NOT_CONTROLLABLE);
  verifyNewTabSettings(ext1, NOT_CONTROLLABLE);

  await ext2.startup();
  ok(
    AboutNewTab.newTabURL.endsWith(NEWTAB_URI_2),
    "newTabURL is overridden by the second extension."
  );
  await checkNewTabPageOverride(ext1, NEWTAB_URI_2, CONTROLLED_BY_OTHER);
  verifyNewTabSettings(ext2, CONTROLLED_BY_THIS);

  // Verify that calling set and clear do nothing.
  ext2.sendMessage("trySet");
  await ext2.awaitMessage("newTabPageSet");
  await checkNewTabPageOverride(ext1, NEWTAB_URI_2, CONTROLLED_BY_OTHER);
  verifyNewTabSettings(ext2, CONTROLLED_BY_THIS);

  ext2.sendMessage("tryClear");
  await ext2.awaitMessage("newTabPageCleared");
  await checkNewTabPageOverride(ext1, NEWTAB_URI_2, CONTROLLED_BY_OTHER);
  verifyNewTabSettings(ext2, CONTROLLED_BY_THIS);

  // Disable the second extension.
  let addon = await AddonManager.getAddonByID(EXT_2_ID);
  let disabledPromise = awaitEvent("shutdown");
  await addon.disable();
  await disabledPromise;
  equal(
    AboutNewTab.newTabURL,
    DEFAULT_NEW_TAB_URL,
    "newTabURL url is reset to the default after second extension is disabled."
  );
  await checkNewTabPageOverride(ext1, AboutNewTab.newTabURL, NOT_CONTROLLABLE);
  verifyNewTabSettings(ext1, NOT_CONTROLLABLE);

  // Re-enable the second extension.
  let enabledPromise = awaitEvent("ready");
  await addon.enable();
  await enabledPromise;
  ok(
    AboutNewTab.newTabURL.endsWith(NEWTAB_URI_2),
    "newTabURL is overridden by the second extension."
  );
  await checkNewTabPageOverride(ext2, NEWTAB_URI_2, CONTROLLED_BY_THIS);
  verifyNewTabSettings(ext2, CONTROLLED_BY_THIS);

  await ext1.unload();
  ok(
    AboutNewTab.newTabURL.endsWith(NEWTAB_URI_2),
    "newTabURL is still overridden by the second extension."
  );
  await checkNewTabPageOverride(ext2, NEWTAB_URI_2, CONTROLLED_BY_THIS);
  verifyNewTabSettings(ext2, CONTROLLED_BY_THIS);

  await ext3.startup();
  ok(
    AboutNewTab.newTabURL.endsWith(NEWTAB_URI_3),
    "newTabURL is overridden by the third extension."
  );
  await checkNewTabPageOverride(ext2, NEWTAB_URI_3, CONTROLLED_BY_OTHER);
  verifyNewTabSettings(ext3, CONTROLLED_BY_THIS);

  // Disable the second extension.
  disabledPromise = awaitEvent("shutdown");
  await addon.disable();
  await disabledPromise;
  ok(
    AboutNewTab.newTabURL.endsWith(NEWTAB_URI_3),
    "newTabURL is still overridden by the third extension."
  );
  await checkNewTabPageOverride(ext3, NEWTAB_URI_3, CONTROLLED_BY_THIS);
  verifyNewTabSettings(ext3, CONTROLLED_BY_THIS);

  // Re-enable the second extension.
  enabledPromise = awaitEvent("ready");
  await addon.enable();
  await enabledPromise;
  ok(
    AboutNewTab.newTabURL.endsWith(NEWTAB_URI_3),
    "newTabURL is still overridden by the third extension."
  );
  await checkNewTabPageOverride(ext3, NEWTAB_URI_3, CONTROLLED_BY_THIS);
  verifyNewTabSettings(ext3, CONTROLLED_BY_THIS);

  await ext3.unload();
  ok(
    AboutNewTab.newTabURL.endsWith(NEWTAB_URI_2),
    "newTabURL reverts to being overridden by the second extension."
  );
  await checkNewTabPageOverride(ext2, NEWTAB_URI_2, CONTROLLED_BY_THIS);
  verifyNewTabSettings(ext2, CONTROLLED_BY_THIS);

  await ext2.unload();
  equal(
    AboutNewTab.newTabURL,
    DEFAULT_NEW_TAB_URL,
    "newTabURL url is reset to the default."
  );
  ok(
    !Services.prefs.prefHasUserValue(NEW_TAB_PRIVATE_ALLOWED),
    "controlled flag reset"
  );
  ok(
    !Services.prefs.prefHasUserValue(NEW_TAB_EXTENSION_CONTROLLED),
    "controlled flag reset"
  );

  await promiseShutdownManager();
});

// Tests that we handle the upgrade/downgrade process correctly
// when an extension is installed temporarily on top of a permanently
// installed one.
add_task(async function test_temporary_installation() {
  const ID = "newtab@tests.mozilla.org";
  const PAGE1 = "page1.html";
  const PAGE2 = "page2.html";

  equal(
    AboutNewTab.newTabURL,
    DEFAULT_NEW_TAB_URL,
    "newTabURL is set to the default."
  );

  await promiseStartupManager();

  let permanent = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: {
        gecko: { id: ID },
      },
      chrome_url_overrides: {
        newtab: PAGE1,
      },
    },
    useAddonManager: "permanent",
  });

  await permanent.startup();
  ok(
    AboutNewTab.newTabURL.endsWith(PAGE1),
    "newTabURL is overridden by permanent extension."
  );

  let temporary = ExtensionTestUtils.loadExtension({
    manifest: {
      browser_specific_settings: {
        gecko: { id: ID },
      },
      chrome_url_overrides: {
        newtab: PAGE2,
      },
    },
    useAddonManager: "temporary",
  });

  await temporary.startup();
  ok(
    AboutNewTab.newTabURL.endsWith(PAGE2),
    "newTabURL is overridden by temporary extension."
  );

  await promiseRestartManager();
  await permanent.awaitStartup();

  ok(
    AboutNewTab.newTabURL.endsWith(PAGE1),
    "newTabURL is back to the value set by permanent extension."
  );

  await permanent.unload();

  equal(
    AboutNewTab.newTabURL,
    DEFAULT_NEW_TAB_URL,
    "newTabURL is set back to the default."
  );
  await promiseShutdownManager();
});
