/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

let bounceTrackingProtection = Cc[
  "@mozilla.org/bounce-tracking-protection;1"
].getService(Ci.nsIBounceTrackingProtection);

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      [
        "privacy.bounceTrackingProtection.mode",
        Ci.nsIBounceTrackingProtection.MODE_ENABLED,
      ],
      ["privacy.bounceTrackingProtection.bounceTrackingGracePeriodSec", 0],
    ],
  });
});

// Tests a stateless bounce via client redirect.
add_task(async function test_client_bounce_simple() {
  await runTestBounce({ bounceType: "client" });
});

// Tests a stateless bounce via server redirect.
add_task(async function test_server_bounce_simple() {
  await runTestBounce({ bounceType: "server" });
});

// Tests a chained redirect consisting of a server and a client redirect.
add_task(async function test_bounce_chain() {
  Assert.equal(
    bounceTrackingProtection.testGetBounceTrackerCandidateHosts({}).length,
    0,
    "No bounce tracker hosts initially."
  );
  Assert.equal(
    bounceTrackingProtection.testGetUserActivationHosts({}).length,
    0,
    "No user activation hosts initially."
  );

  await BrowserTestUtils.withNewTab(
    getBaseUrl(ORIGIN_A) + "file_start.html",
    async browser => {
      let promiseRecordBounces = waitForRecordBounces(browser);

      // The final destination after the bounces.
      let targetURL = new URL(getBaseUrl(ORIGIN_B) + "file_start.html");

      // Construct last hop.
      let bounceChainUrlEnd = getBounceURL({ bounceType: "server", targetURL });
      // Construct first hop, nesting last hop.
      let bounceChainUrlFull = getBounceURL({
        bounceType: "client",
        redirectDelayMS: 100,
        bounceOrigin: ORIGIN_TRACKER_B,
        targetURL: bounceChainUrlEnd,
      });

      info("bounceChainUrl: " + bounceChainUrlFull.href);

      // Navigate through the bounce chain.
      await navigateLinkClick(browser, bounceChainUrlFull);

      // Wait for the final site to be loaded which complete the BounceTrackingRecord.
      await BrowserTestUtils.browserLoaded(browser, false, targetURL);

      // Navigate again with user gesture which triggers
      // BounceTrackingProtection::RecordStatefulBounces. We could rely on the
      // timeout (mClientBounceDetectionTimeout) here but that can cause races
      // in debug where the load is quite slow.
      await navigateLinkClick(
        browser,
        new URL(getBaseUrl(ORIGIN_C) + "file_start.html")
      );

      await promiseRecordBounces;

      Assert.deepEqual(
        bounceTrackingProtection
          .testGetBounceTrackerCandidateHosts({})
          .map(entry => entry.siteHost)
          .sort(),
        [SITE_TRACKER_B, SITE_TRACKER].sort(),
        `Identified all bounce trackers in the redirect chain.`
      );
      Assert.deepEqual(
        bounceTrackingProtection
          .testGetUserActivationHosts({})
          .map(entry => entry.siteHost)
          .sort(),
        [SITE_A, SITE_B].sort(),
        "Should only have user activation for sites where we clicked links."
      );

      let purgedHosts =
        await bounceTrackingProtection.testRunPurgeBounceTrackers();
      Assert.deepEqual(
        purgedHosts.sort(),
        [SITE_TRACKER, SITE_TRACKER_B].sort(),
        "Should purge state for both bounce trackers in the chain."
      );

      let purgeLog = bounceTrackingProtection.testGetRecentlyPurgedTrackers({});
      Assert.equal(
        purgeLog.length,
        2,
        "Should have two entries in the purge log."
      );
      for (let entry of purgeLog) {
        let record = entry.bounceTrackingRecord;
        Assert.ok(
          record,
          `Purge log entry for ${entry.siteHost} should have a bounceTrackingRecord.`
        );
        Assert.equal(
          record.initialHost,
          SITE_A,
          "Each record should have initialHost = SITE_A."
        );
        Assert.equal(
          record.finalHost,
          SITE_B,
          "Each record should have finalHost = SITE_B."
        );
        Assert.ok(
          record.bounceHosts.includes(SITE_TRACKER),
          `Record for ${entry.siteHost} should include ${SITE_TRACKER} in bounce hosts.`
        );
        Assert.ok(
          record.bounceHosts.includes(SITE_TRACKER_B),
          `Record for ${entry.siteHost} should include ${SITE_TRACKER_B} in bounce hosts.`
        );
      }

      bounceTrackingProtection.clearAll();
    }
  );
});
