"use strict";

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

// In this test, we want to check the behavior of extensions without private
// browsing access. Privileged add-ons automatically have private browsing
// access, so make sure that the test add-ons are not privileged.
AddonTestUtils.usePrivilegedSignatures = false;

add_task(async function setup() {
  await ExtensionTestUtils.startAddonManager();

  Services.prefs.setBoolPref("extensions.eventPages.enabled", true);
});

function createTestExtension({ privateAllowed }) {
  return ExtensionTestUtils.loadExtension({
    incognitoOverride: privateAllowed ? "spanning" : null,
    manifest: {
      permissions: ["cookies"],
      host_permissions: ["https://example.com/"],
      background: { persistent: false },
    },
    background() {
      browser.cookies.onChanged.addListener(changeInfo => {
        browser.test.sendMessage("cookie-event", changeInfo);
      });
    },
  });
}

function addAndRemoveCookie({ isPrivate }) {
  const cookie = {
    name: "cookname",
    value: "cookvalue",
    domain: "example.com",
    hostOnly: true,
    path: "/",
    secure: true,
    httpOnly: false,
    sameSite: "lax",
    session: false,
    firstPartyDomain: "",
    partitionKey: null,
    expirationDate: Math.floor(Date.now() / 1000) + 3600,
    storeId: isPrivate ? "firefox-private" : "firefox-default",
  };
  const originAttributes = { privateBrowsingId: isPrivate ? 1 : 0 };
  Services.cookies.add(
    cookie.domain,
    cookie.path,
    cookie.name,
    cookie.value,
    cookie.secure,
    cookie.httpOnly,
    cookie.session,
    cookie.expirationDate * 1000,
    originAttributes,
    Ci.nsICookie.SAMESITE_LAX,
    Ci.nsICookie.SCHEME_HTTPS
  );
  Services.cookies.remove(
    cookie.domain,
    cookie.name,
    cookie.path,
    originAttributes
  );
  return cookie;
}

add_task(async function test_onChanged_event_page() {
  let nonPrivateExtension = createTestExtension({ privateAllowed: false });
  let privateExtension = createTestExtension({ privateAllowed: true });
  await privateExtension.startup();
  await nonPrivateExtension.startup();
  assertPersistentListeners(privateExtension, "cookies", "onChanged", {
    primed: false,
  });
  assertPersistentListeners(nonPrivateExtension, "cookies", "onChanged", {
    primed: false,
  });

  // Suspend both event pages.
  await privateExtension.terminateBackground();
  assertPersistentListeners(privateExtension, "cookies", "onChanged", {
    primed: true,
  });
  await nonPrivateExtension.terminateBackground();
  assertPersistentListeners(nonPrivateExtension, "cookies", "onChanged", {
    primed: true,
  });

  // Modifying a private cookie should wake up the private extension, but not
  // the other one that does not have access to private browsing data.
  let privateCookie = addAndRemoveCookie({ isPrivate: true });

  Assert.deepEqual(
    await privateExtension.awaitMessage("cookie-event"),
    { removed: false, cookie: privateCookie, cause: "explicit" },
    "cookies.onChanged for private cookie creation"
  );
  Assert.deepEqual(
    await privateExtension.awaitMessage("cookie-event"),
    { removed: true, cookie: privateCookie, cause: "explicit" },
    "cookies.onChanged for private cookie removal"
  );
  // Private extension should have awakened...
  assertPersistentListeners(privateExtension, "cookies", "onChanged", {
    primed: false,
  });
  // ... but the non-private extension should still be sound asleep.
  assertPersistentListeners(nonPrivateExtension, "cookies", "onChanged", {
    primed: true,
  });

  // A non-private cookie modification should notify both extensions.
  let nonPrivateCookie = addAndRemoveCookie({ isPrivate: false });
  Assert.deepEqual(
    await privateExtension.awaitMessage("cookie-event"),
    { removed: false, cookie: nonPrivateCookie, cause: "explicit" },
    "cookies.onChanged for cookie creation in privateExtension"
  );
  Assert.deepEqual(
    await privateExtension.awaitMessage("cookie-event"),
    { removed: true, cookie: nonPrivateCookie, cause: "explicit" },
    "cookies.onChanged for cookie removal in privateExtension"
  );
  Assert.deepEqual(
    await nonPrivateExtension.awaitMessage("cookie-event"),
    { removed: false, cookie: nonPrivateCookie, cause: "explicit" },
    "cookies.onChanged for cookie creation in nonPrivateExtension"
  );
  Assert.deepEqual(
    await nonPrivateExtension.awaitMessage("cookie-event"),
    { removed: true, cookie: nonPrivateCookie, cause: "explicit" },
    "cookies.onChanged for cookie removal in nonPrivateCookie"
  );

  await privateExtension.unload();
  await nonPrivateExtension.unload();
});
