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

"use strict";

const TRACKING_PAGE =
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
const BENIGN_PAGE =
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html";
const COOKIE_PAGE =
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html";

const CM_PREF = "privacy.trackingprotection.cryptomining.enabled";
const FP_PREF = "privacy.trackingprotection.fingerprinting.enabled";
const TP_PREF = "privacy.trackingprotection.enabled";
const CB_PREF = "network.cookie.cookieBehavior";

const PREF_REPORT_BREAKAGE_URL = "browser.contentblocking.reportBreakage.url";

let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
let { CommonUtils } = ChromeUtils.importESModule(
  "resource://services-common/utils.sys.mjs"
);
let { Preferences } = ChromeUtils.importESModule(
  "resource://gre/modules/Preferences.sys.mjs"
);

add_setup(async function () {
  await UrlClassifierTestUtils.addTestTrackers();

  registerCleanupFunction(() => {
    // Clear prefs that are touched in this test again for sanity.
    Services.prefs.clearUserPref(TP_PREF);
    Services.prefs.clearUserPref(CB_PREF);
    Services.prefs.clearUserPref(FP_PREF);
    Services.prefs.clearUserPref(CM_PREF);
    Services.prefs.clearUserPref(PREF_REPORT_BREAKAGE_URL);

    UrlClassifierTestUtils.cleanupTestTrackers();
  });

  await SpecialPowers.pushPrefEnv({
    set: [
      [
        "urlclassifier.features.fingerprinting.blacklistHosts",
        "fingerprinting.example.com",
      ],
      [
        "urlclassifier.features.fingerprinting.annotate.blacklistHosts",
        "fingerprinting.example.com",
      ],
      ["privacy.trackingprotection.cryptomining.enabled", true],
      [
        "urlclassifier.features.cryptomining.blacklistHosts",
        "cryptomining.example.com",
      ],
      [
        "urlclassifier.features.cryptomining.annotate.blacklistHosts",
        "cryptomining.example.com",
      ],
    ],
  });
});

add_task(async function testReportBreakageCancel() {
  Services.prefs.setBoolPref(TP_PREF, true);

  await BrowserTestUtils.withNewTab(TRACKING_PAGE, async function () {
    await openProtectionsPanel();
    await TestUtils.waitForCondition(() =>
      gProtectionsHandler._protectionsPopup.hasAttribute("blocking")
    );

    let siteNotWorkingButton = document.getElementById(
      "protections-popup-tp-switch-breakage-link"
    );
    ok(
      BrowserTestUtils.is_visible(siteNotWorkingButton),
      "site not working button is visible"
    );
    let siteNotWorkingView = document.getElementById(
      "protections-popup-siteNotWorkingView"
    );
    let viewShown = BrowserTestUtils.waitForEvent(
      siteNotWorkingView,
      "ViewShown"
    );
    siteNotWorkingButton.click();
    await viewShown;

    let sendReportButton = document.getElementById(
      "protections-popup-siteNotWorkingView-sendReport"
    );
    let sendReportView = document.getElementById(
      "protections-popup-sendReportView"
    );
    viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
    sendReportButton.click();
    await viewShown;

    ok(true, "Report breakage view was shown");

    viewShown = BrowserTestUtils.waitForEvent(siteNotWorkingView, "ViewShown");
    let cancelButton = document.getElementById(
      "protections-popup-sendReportView-cancel"
    );
    cancelButton.click();
    await viewShown;

    ok(true, "Main view was shown");
  });

  Services.prefs.clearUserPref(TP_PREF);
});

add_task(async function testReportBreakageSiteException() {
  Services.prefs.setBoolPref(TP_PREF, true);

  let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";

  await BrowserTestUtils.withNewTab(url, async browser => {
    let loaded = BrowserTestUtils.browserLoaded(browser, false);
    gProtectionsHandler.disableForCurrentPage();
    await loaded;

    await openProtectionsPanel();

    let siteFixedButton = document.getElementById(
      "protections-popup-tp-switch-breakage-fixed-link"
    );
    ok(
      BrowserTestUtils.is_visible(siteFixedButton),
      "site fixed button is visible"
    );
    let sendReportView = document.getElementById(
      "protections-popup-sendReportView"
    );
    let viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
    siteFixedButton.click();
    await viewShown;

    ok(true, "Report breakage view was shown");

    await testReportBreakageSubmit(
      TRACKING_PAGE,
      "trackingprotection",
      false,
      true
    );

    // Pass false for shouldReload - there's no need since the tab is going away.
    gProtectionsHandler.enableForCurrentPage(false);
  });

  Services.prefs.clearUserPref(TP_PREF);
});

add_task(async function testNoTracking() {
  await BrowserTestUtils.withNewTab(BENIGN_PAGE, async function () {
    await openProtectionsPanel();

    let siteNotWorkingButton = document.getElementById(
      "protections-popup-tp-switch-breakage-link"
    );
    ok(
      BrowserTestUtils.is_hidden(siteNotWorkingButton),
      "site not working button is not visible"
    );
  });
});

add_task(async function testReportBreakageError() {
  Services.prefs.setBoolPref(TP_PREF, true);
  // Make sure that we correctly strip the query.
  let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
  await BrowserTestUtils.withNewTab(url, async function () {
    await openAndTestReportBreakage(TRACKING_PAGE, "trackingprotection", true);
  });

  Services.prefs.clearUserPref(TP_PREF);
});

add_task(async function testTP() {
  Services.prefs.setBoolPref(TP_PREF, true);
  // Make sure that we correctly strip the query.
  let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
  await BrowserTestUtils.withNewTab(url, async function () {
    await openAndTestReportBreakage(TRACKING_PAGE, "trackingprotection");
  });

  Services.prefs.clearUserPref(TP_PREF);
});

add_task(async function testCR() {
  Services.prefs.setIntPref(
    CB_PREF,
    Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
  );
  // Make sure that we correctly strip the query.
  let url = COOKIE_PAGE + "?a=b&1=abc&unicode=🦊";
  await BrowserTestUtils.withNewTab(url, async function () {
    await openAndTestReportBreakage(COOKIE_PAGE, "cookierestrictions");
  });

  Services.prefs.clearUserPref(CB_PREF);
});

add_task(async function testFP() {
  Services.prefs.setIntPref(CB_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
  Services.prefs.setBoolPref(FP_PREF, true);
  // Make sure that we correctly strip the query.
  let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
  await BrowserTestUtils.withNewTab(url, async function (browser) {
    let promise = waitForContentBlockingEvent();
    await SpecialPowers.spawn(browser, [], function () {
      content.postMessage("fingerprinting", "*");
    });
    await promise;

    await openAndTestReportBreakage(TRACKING_PAGE, "fingerprinting", true);
  });

  Services.prefs.clearUserPref(FP_PREF);
  Services.prefs.clearUserPref(CB_PREF);
});

add_task(async function testCM() {
  Services.prefs.setIntPref(CB_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
  Services.prefs.setBoolPref(CM_PREF, true);
  // Make sure that we correctly strip the query.
  let url = TRACKING_PAGE + "?a=b&1=abc&unicode=🦊";
  await BrowserTestUtils.withNewTab(url, async function (browser) {
    let promise = waitForContentBlockingEvent();
    await SpecialPowers.spawn(browser, [], function () {
      content.postMessage("cryptomining", "*");
    });
    await promise;

    await openAndTestReportBreakage(TRACKING_PAGE, "cryptomining", true);
  });

  Services.prefs.clearUserPref(CM_PREF);
  Services.prefs.clearUserPref(CB_PREF);
});

async function openAndTestReportBreakage(url, tags, error = false) {
  await openProtectionsPanel();

  let siteNotWorkingButton = document.getElementById(
    "protections-popup-tp-switch-breakage-link"
  );
  ok(
    BrowserTestUtils.is_visible(siteNotWorkingButton),
    "site not working button is visible"
  );
  let siteNotWorkingView = document.getElementById(
    "protections-popup-siteNotWorkingView"
  );
  let viewShown = BrowserTestUtils.waitForEvent(
    siteNotWorkingView,
    "ViewShown"
  );
  siteNotWorkingButton.click();
  await viewShown;

  let sendReportButton = document.getElementById(
    "protections-popup-siteNotWorkingView-sendReport"
  );
  let sendReportView = document.getElementById(
    "protections-popup-sendReportView"
  );
  viewShown = BrowserTestUtils.waitForEvent(sendReportView, "ViewShown");
  sendReportButton.click();
  await viewShown;

  ok(true, "Report breakage view was shown");

  await testReportBreakageSubmit(url, tags, error, false);
}

// This function assumes that the breakage report view is ready.
async function testReportBreakageSubmit(url, tags, error, hasException) {
  // Setup a mock server for receiving breakage reports.
  let server = new HttpServer();
  server.start(-1);
  let i = server.identity;
  let path =
    i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort + "/";

  Services.prefs.setStringPref(PREF_REPORT_BREAKAGE_URL, path);

  let comments = document.getElementById(
    "protections-popup-sendReportView-collection-comments"
  );
  is(comments.value, "", "Comments textarea should initially be empty");

  let submitButton = document.getElementById(
    "protections-popup-sendReportView-submit"
  );
  let reportURL = document.getElementById(
    "protections-popup-sendReportView-collection-url"
  ).value;

  is(reportURL, url, "Shows the correct URL in the report UI.");

  // Make sure that sending the report closes the identity popup.
  let popuphidden = BrowserTestUtils.waitForEvent(
    gProtectionsHandler._protectionsPopup,
    "popuphidden"
  );

  // Check that we're receiving a good report.
  await new Promise(resolve => {
    server.registerPathHandler("/", async (request, response) => {
      is(request.method, "POST", "request was a post");

      // Extract and "parse" the form data in the request body.
      let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
      let boundary = request
        .getHeader("Content-Type")
        .match(/boundary=-+([^-]*)/i)[1];
      let regex = new RegExp("-+" + boundary + "-*\\s+");
      let sections = body.split(regex);

      let prefs = [
        "privacy.trackingprotection.enabled",
        "privacy.trackingprotection.pbmode.enabled",
        "urlclassifier.trackingTable",
        "network.http.referer.defaultPolicy",
        "network.http.referer.defaultPolicy.pbmode",
        "network.cookie.cookieBehavior",
        "privacy.annotate_channels.strict_list.enabled",
        "privacy.restrict3rdpartystorage.expiration",
        "privacy.trackingprotection.fingerprinting.enabled",
        "privacy.trackingprotection.cryptomining.enabled",
      ];
      let prefsBody = "";

      for (let pref of prefs) {
        prefsBody += `${pref}: ${Preferences.get(pref)}\r\n`;
      }

      Assert.deepEqual(
        sections,
        [
          "",
          `Content-Disposition: form-data; name=\"title\"\r\n\r\n${
            Services.io.newURI(reportURL).host
          }\r\n`,
          'Content-Disposition: form-data; name="body"\r\n\r\n' +
            `Full URL: ${reportURL + "?"}\r\n` +
            `userAgent: ${navigator.userAgent}\r\n\r\n` +
            "**Preferences**\r\n" +
            `${prefsBody}\r\n` +
            `hasException: ${hasException}\r\n\r\n` +
            "**Comments**\r\n" +
            "This is a comment\r\n",
          'Content-Disposition: form-data; name="labels"\r\n\r\n' +
            `${hasException ? "" : tags}\r\n`,
          "",
        ],
        "Should send the correct form data"
      );

      if (error) {
        response.setStatusLine(request.httpVersion, 500, "Request failed");
      } else {
        response.setStatusLine(request.httpVersion, 201, "Entry created");
      }

      resolve();
    });

    comments.value = "This is a comment";
    submitButton.click();
  });

  let errorMessage = document.getElementById(
    "protections-popup-sendReportView-report-error"
  );
  if (error) {
    await TestUtils.waitForCondition(() =>
      BrowserTestUtils.is_visible(errorMessage)
    );
    is(
      comments.value,
      "This is a comment",
      "Comment not cleared in case of an error"
    );
    gProtectionsHandler._protectionsPopup.hidePopup();
  } else {
    ok(BrowserTestUtils.is_hidden(errorMessage), "Error message not shown");
  }

  await popuphidden;

  // Stop the server.
  await new Promise(r => server.stop(r));

  Services.prefs.clearUserPref(PREF_REPORT_BREAKAGE_URL);
}
