/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

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

const TEST_DOMAIN = "https://example.com/";
const TEST_EMAIL_WEBAPP_DOMAIN = "https://test1.example.com/";
const EMAIL_TRACKER_DOMAIN = "https://email-tracking.example.org/";
const TEST_PATH = "browser/toolkit/components/url-classifier/tests/browser/";

const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "page.html";
const TEST_EMAIL_WEBAPP_PAGE =
  TEST_EMAIL_WEBAPP_DOMAIN + TEST_PATH + "page.html";

const EMAIL_TRACKER_PAGE = EMAIL_TRACKER_DOMAIN + TEST_PATH + "page.html";
const EMAIL_TRACKER_IMAGE = EMAIL_TRACKER_DOMAIN + TEST_PATH + "raptor.jpg";

const TELEMETRY_EMAIL_TRACKER_COUNT = "EMAIL_TRACKER_COUNT";
const TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB =
  "EMAIL_TRACKER_EMBEDDED_PER_TAB";

const LABEL_BASE_NORMAL = 0;
const LABEL_CONTENT_NORMAL = 1;
const LABEL_BASE_EMAIL_WEBAPP = 2;
const LABEL_CONTENT_EMAIL_WEBAPP = 3;

const KEY_BASE_NORMAL = "base_normal";
const KEY_CONTENT_NORMAL = "content_normal";
const KEY_ALL_NORMAL = "all_normal";
const KEY_BASE_EMAILAPP = "base_emailapp";
const KEY_CONTENT_EMAILAPP = "content_emailapp";
const KEY_ALL_EMAILAPP = "all_emailapp";

async function clearTelemetry() {
  Services.telemetry.getSnapshotForHistograms("main", true /* clear */);
  Services.telemetry.getHistogramById(TELEMETRY_EMAIL_TRACKER_COUNT).clear();
  Services.telemetry
    .getKeyedHistogramById(TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB)
    .clear();
}

async function loadImage(browser, url) {
  return SpecialPowers.spawn(browser, [url], page => {
    return new Promise(resolve => {
      let image = new content.Image();
      image.src = page + "?" + Math.random();
      image.onload = _ => resolve(true);
      image.onerror = _ => resolve(false);
    });
  });
}

async function getTelemetryProbe(key, label, checkCntFn) {
  let histogram;

  // Wait until the telemetry probe appears.
  await TestUtils.waitForCondition(() => {
    let histograms = Services.telemetry.getSnapshotForHistograms(
      "main",
      false /* clear */
    ).parent;

    histogram = histograms[key];

    let checkRes = false;

    if (histogram) {
      checkRes = checkCntFn ? checkCntFn(histogram.values[label]) : true;
    }

    return checkRes;
  });

  return histogram.values[label] || 0;
}

async function getKeyedHistogram(histogram_id, key, bucket, checkCntFn) {
  let histogram;

  // Wait until the telemetry probe appears.
  await TestUtils.waitForCondition(() => {
    let histograms = Services.telemetry.getSnapshotForKeyedHistograms(
      "main",
      false /* clear */
    ).parent;

    histogram = histograms[histogram_id];

    let checkRes = false;

    if (histogram && histogram[key]) {
      checkRes = checkCntFn ? checkCntFn(histogram[key].values[bucket]) : true;
    }

    return checkRes;
  });

  return histogram[key].values[bucket] || 0;
}

async function checkTelemetryProbe(key, label, expectedCnt) {
  let cnt = await getTelemetryProbe(key, label, cnt => {
    if (cnt === undefined) {
      cnt = 0;
    }

    return cnt == expectedCnt;
  });

  is(cnt, expectedCnt, "There should be expected count in telemetry.");
}

async function checkKeyedHistogram(histogram_id, key, bucket, expectedCnt) {
  let cnt = await getKeyedHistogram(histogram_id, key, bucket, cnt => {
    if (cnt === undefined) {
      cnt = 0;
    }

    return cnt == expectedCnt;
  });

  is(cnt, expectedCnt, "There should be expected count in keyed telemetry.");
}

function checkNoTelemetryProbe(key) {
  let histograms = Services.telemetry.getSnapshotForHistograms(
    "main",
    false /* clear */
  ).parent;

  let histogram = histograms[key];

  ok(!histogram, `No Telemetry has been recorded for ${key}`);
}

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      [
        "urlclassifier.features.emailtracking.datacollection.blocklistTables",
        "mochitest5-track-simple",
      ],
      [
        "urlclassifier.features.emailtracking.datacollection.allowlistTables",
        "",
      ],
      [
        "urlclassifier.features.emailtracking.blocklistTables",
        "mochitest5-track-simple",
      ],
      ["urlclassifier.features.emailtracking.allowlistTables", ""],
      ["privacy.trackingprotection.enabled", false],
      ["privacy.trackingprotection.annotate_channels", false],
      ["privacy.trackingprotection.cryptomining.enabled", false],
      ["privacy.trackingprotection.emailtracking.enabled", true],
      [
        "privacy.trackingprotection.emailtracking.data_collection.enabled",
        true,
      ],
      ["privacy.trackingprotection.fingerprinting.enabled", false],
      ["privacy.trackingprotection.socialtracking.enabled", false],
      [
        "privacy.trackingprotection.emailtracking.webapp.domains",
        "test1.example.com",
      ],
    ],
  });

  await UrlClassifierTestUtils.addTestTrackers();

  registerCleanupFunction(function () {
    UrlClassifierTestUtils.cleanupTestTrackers();
  });

  await clearTelemetry();
});

add_task(async function test_email_tracking_telemetry() {
  // Open a non email webapp tab.
  await BrowserTestUtils.withNewTab(TEST_PAGE, async browser => {
    // Load a image from the email tracker
    let res = await loadImage(browser, EMAIL_TRACKER_IMAGE);

    is(res, false, "The image is blocked.");

    // Verify the telemetry of the email tracker count.
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_BASE_NORMAL,
      1
    );
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_CONTENT_NORMAL,
      0
    );
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_BASE_EMAIL_WEBAPP,
      0
    );
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_CONTENT_EMAIL_WEBAPP,
      0
    );
  });

  // Open an email webapp tab.
  await BrowserTestUtils.withNewTab(TEST_EMAIL_WEBAPP_PAGE, async browser => {
    // Load a image from the email tracker
    let res = await loadImage(browser, EMAIL_TRACKER_IMAGE);

    is(res, false, "The image is blocked.");

    // Verify the telemetry of the email tracker count.
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_BASE_NORMAL,
      1
    );
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_CONTENT_NORMAL,
      0
    );
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_BASE_EMAIL_WEBAPP,
      1
    );
    await checkTelemetryProbe(
      TELEMETRY_EMAIL_TRACKER_COUNT,
      LABEL_CONTENT_EMAIL_WEBAPP,
      0
    );
  });
  // Make sure the tab was closed properly before clearing Telemetry.
  await BrowserUtils.promiseObserved("window-global-destroyed");

  await clearTelemetry();
});

add_task(async function test_no_telemetry_for_first_party_email_tracker() {
  // Open a email tracker tab.
  await BrowserTestUtils.withNewTab(EMAIL_TRACKER_PAGE, async browser => {
    // Load a image from the first-party email tracker
    let res = await loadImage(browser, EMAIL_TRACKER_IMAGE);

    is(res, true, "The image is loaded.");

    // Verify that there was no telemetry recorded.
    checkNoTelemetryProbe(TELEMETRY_EMAIL_TRACKER_COUNT);
  });
  // Make sure the tab was closed properly before clearing Telemetry.
  await BrowserUtils.promiseObserved("window-global-destroyed");

  await clearTelemetry();
});

add_task(async function test_disable_email_data_collection() {
  // Disable Email Tracking Data Collection.
  await SpecialPowers.pushPrefEnv({
    set: [
      [
        "privacy.trackingprotection.emailtracking.data_collection.enabled",
        false,
      ],
    ],
  });

  // Open an email webapp tab.
  await BrowserTestUtils.withNewTab(TEST_EMAIL_WEBAPP_PAGE, async browser => {
    // Load a image from the email tracker
    let res = await loadImage(browser, EMAIL_TRACKER_IMAGE);

    is(res, false, "The image is blocked.");

    // Verify that there was no telemetry recorded.
    checkNoTelemetryProbe(TELEMETRY_EMAIL_TRACKER_COUNT);
  });
  // Make sure the tab was closed properly before clearing Telemetry.
  await BrowserUtils.promiseObserved("window-global-destroyed");

  await SpecialPowers.popPrefEnv();
  await clearTelemetry();
});

add_task(async function test_email_tracker_embedded_telemetry() {
  // First, we open a page without loading any email trackers.
  await BrowserTestUtils.withNewTab(TEST_PAGE, async _ => {});
  // Make sure the tab was closed properly before checking Telemetry.
  await BrowserUtils.promiseObserved("window-global-destroyed");

  // Check that the telemetry has been record properly for normal page. The
  // telemetry should show there was no email tracker loaded.
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_BASE_NORMAL,
    0,
    1
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_CONTENT_NORMAL,
    0,
    1
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_ALL_NORMAL,
    0,
    1
  );

  // Second, Open a email webapp tab that doesn't a load email tracker.
  await BrowserTestUtils.withNewTab(TEST_EMAIL_WEBAPP_PAGE, async _ => {});
  // Make sure the tab was closed properly before checking Telemetry.
  await BrowserUtils.promiseObserved("window-global-destroyed");

  // Check that the telemetry has been record properly for the email webapp. The
  // telemetry should show there was no email tracker loaded.
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_BASE_EMAILAPP,
    0,
    1
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_CONTENT_EMAILAPP,
    0,
    1
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_ALL_EMAILAPP,
    0,
    1
  );

  // Third, open a page with one email tracker loaded.
  await BrowserTestUtils.withNewTab(TEST_PAGE, async browser => {
    // Load a image from the email tracker
    let res = await loadImage(browser, EMAIL_TRACKER_IMAGE);

    is(res, false, "The image is blocked.");
  });
  // Make sure the tab was closed properly before checking Telemetry.
  await BrowserUtils.promiseObserved("window-global-destroyed");

  // Verify that the telemetry has been record properly, The telemetry should
  // show there was one base email tracker loaded.
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_BASE_NORMAL,
    1,
    1
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_CONTENT_NORMAL,
    0,
    2
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_ALL_NORMAL,
    0,
    1
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_ALL_NORMAL,
    1,
    1
  );

  // Open a page and load the same email tracker multiple times. There
  // should be only one count for the same tracker.
  await BrowserTestUtils.withNewTab(TEST_PAGE, async browser => {
    // Load a image from the email tracker two times.
    await loadImage(browser, EMAIL_TRACKER_IMAGE);
    await loadImage(browser, EMAIL_TRACKER_IMAGE);
  });
  // Make sure the tab was closed properly before checking Telemetry.
  await BrowserUtils.promiseObserved("window-global-destroyed");

  // Verify that there is still only one count when loading the same tracker
  // multiple times.
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_BASE_NORMAL,
    1,
    2
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_CONTENT_NORMAL,
    0,
    3
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_ALL_NORMAL,
    0,
    1
  );
  await checkKeyedHistogram(
    TELEMETRY_EMAIL_TRACKER_EMBEDDED_PER_TAB,
    KEY_ALL_NORMAL,
    1,
    2
  );

  await clearTelemetry();
});
