"use strict";

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

// This test supports both e10s and non-e10s mode. In non-e10s mode, this test
// drives itself by creating a profile directory, setting up the URL classifier
// test tables and adjusting the prefs which are necessary to do the testing.
// In e10s mode however, some of these operations such as creating a profile
// directory, setting up the URL classifier test tables and setting prefs
// aren't supported in the content process, so we split the test into two
// parts, the part testing the normal priority case by setting both prefs to
// false (test_trackingProtection_annotateChannels_wrap1.js), and the part
// testing the lowest priority case by setting both prefs to true
// (test_trackingProtection_annotateChannels_wrap2.js).  These wrapper scripts
// are also in charge of creating the profile directory and setting up the URL
// classifier test tables.
//
// Below where we need to take different actions based on the process type we're
// in, we use runtime.processType to take the correct actions.

const runtime = Services.appinfo;
if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
  do_get_profile();
}

const defaultTopWindowURI = NetUtil.newURI("http://www.example.com/");

function listener(tracking, priority, throttleable, nextTest) {
  this._tracking = tracking;
  this._priority = priority;
  this._throttleable = throttleable;
  this._nextTest = nextTest;
}
listener.prototype = {
  onStartRequest(request) {
    Assert.equal(
      request
        .QueryInterface(Ci.nsIClassifiedChannel)
        .isThirdPartyTrackingResource(),
      this._tracking,
      "tracking flag"
    );
    Assert.equal(
      request.QueryInterface(Ci.nsISupportsPriority).priority,
      this._priority,
      "channel priority"
    );
    if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT && this._tracking) {
      Assert.equal(
        !!(
          request.QueryInterface(Ci.nsIClassOfService).classFlags &
          Ci.nsIClassOfService.Throttleable
        ),
        this._throttleable,
        "throttleable flag"
      );
    }
    request.cancel(Cr.NS_ERROR_ABORT);
    this._nextTest();
  },
  onDataAvailable: () => {},
  onStopRequest: () => {},
};

var httpServer;
var normalOrigin, trackingOrigin;
var testPriorityMap;
var currentTest;
// When this test is running in e10s mode, the parent process is in charge of
// setting the prefs for us, so here we merely read our prefs, and if they have
// been set we skip the normal priority test and only test the lowest priority
// case, and if it they have not been set we skip the lowest priority test and
// only test the normal priority case.
// In non-e10s mode, both of these will remain false and we adjust the prefs
// ourselves and test both of the cases in one go.
var skipNormalPriority = false,
  skipLowestPriority = false;

function setup_test() {
  httpServer = new HttpServer();
  httpServer.start(-1);
  httpServer.identity.setPrimary(
    "http",
    "tracking.example.org",
    httpServer.identity.primaryPort
  );
  httpServer.identity.add(
    "http",
    "example.org",
    httpServer.identity.primaryPort
  );
  normalOrigin = "http://localhost:" + httpServer.identity.primaryPort;
  trackingOrigin =
    "http://tracking.example.org:" + httpServer.identity.primaryPort;

  if (runtime.processType == runtime.PROCESS_TYPE_CONTENT) {
    if (
      Services.prefs.getBoolPref(
        "privacy.trackingprotection.annotate_channels"
      ) &&
      Services.prefs.getBoolPref(
        "privacy.trackingprotection.lower_network_priority"
      )
    ) {
      skipNormalPriority = true;
    } else {
      skipLowestPriority = true;
    }
  }

  runTests();
}

function doPriorityTest() {
  if (!testPriorityMap.length) {
    runTests();
    return;
  }

  currentTest = testPriorityMap.shift();

  // Let's be explicit about what we're testing!
  Assert.ok(
    "loadingPrincipal" in currentTest,
    "check for incomplete test case"
  );
  Assert.ok("topWindowURI" in currentTest, "check for incomplete test case");

  var channel = makeChannel(
    currentTest.path,
    currentTest.loadingPrincipal,
    currentTest.topWindowURI
  );
  channel.asyncOpen(
    new listener(
      currentTest.expectedTracking,
      currentTest.expectedPriority,
      currentTest.expectedThrottleable,
      doPriorityTest
    )
  );
}

function makeChannel(path, loadingPrincipal, topWindowURI) {
  var chan;

  if (loadingPrincipal) {
    chan = NetUtil.newChannel({
      uri: path,
      loadingPrincipal,
      securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
      contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
    });
  } else {
    chan = NetUtil.newChannel({
      uri: path,
      loadUsingSystemPrincipal: true,
    });
  }
  chan.QueryInterface(Ci.nsIHttpChannel);
  chan.requestMethod = "GET";
  if (topWindowURI) {
    chan
      .QueryInterface(Ci.nsIHttpChannelInternal)
      .setTopWindowURIIfUnknown(topWindowURI);
  }
  return chan;
}

var tests = [
  // Create the HTTP server.
  setup_test,

  // Add the test table into tracking protection table.
  function addTestTrackers() {
    if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
      UrlClassifierTestUtils.addTestTrackers().then(() => {
        runTests();
      });
    } else {
      runTests();
    }
  },

  // Annotations OFF, normal loading principal, topWinURI of example.com
  // => trackers should not be de-prioritized
  function setupAnnotationsOff() {
    if (skipNormalPriority) {
      runTests();
      return;
    }
    if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
      Services.prefs.setBoolPref(
        "privacy.trackingprotection.annotate_channels",
        false
      );
      Services.prefs.setBoolPref(
        "privacy.trackingprotection.lower_network_priority",
        false
      );
    }
    var principal =
      Services.scriptSecurityManager.createContentPrincipalFromOrigin(
        normalOrigin
      );
    testPriorityMap = [
      {
        path: normalOrigin + "/innocent.css",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
      {
        path: normalOrigin + "/innocent.js",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
      {
        path: trackingOrigin + "/evil.css",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
      {
        path: trackingOrigin + "/evil.js",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
    ];
    // We add the doPriorityTest test here so that it only gets injected in the
    // test list if we're not skipping over this test.
    tests.unshift(doPriorityTest);
    runTests();
  },

  // Annotations ON, normal loading principal, topWinURI of example.com
  // => trackers should be de-prioritized
  function setupAnnotationsOn() {
    if (skipLowestPriority) {
      runTests();
      return;
    }
    if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
      Services.prefs.setBoolPref(
        "privacy.trackingprotection.annotate_channels",
        true
      );
      Services.prefs.setBoolPref(
        "privacy.trackingprotection.lower_network_priority",
        true
      );
    }
    var principal =
      Services.scriptSecurityManager.createContentPrincipalFromOrigin(
        normalOrigin
      );
    testPriorityMap = [
      {
        path: normalOrigin + "/innocent.css",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
      {
        path: normalOrigin + "/innocent.js",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
      {
        path: trackingOrigin + "/evil.css",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: true,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_LOWEST,
        expectedThrottleable: true,
      },
      {
        path: trackingOrigin + "/evil.js",
        loadingPrincipal: principal,
        topWindowURI: defaultTopWindowURI,
        expectedTracking: true,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_LOWEST,
        expectedThrottleable: true,
      },
    ];
    // We add the doPriorityTest test here so that it only gets injected in the
    // test list if we're not skipping over this test.
    tests.unshift(doPriorityTest);
    runTests();
  },

  // Annotations ON, system loading principal, topWinURI of example.com
  // => trackers should not be de-prioritized
  function setupAnnotationsOnSystemPrincipal() {
    if (skipLowestPriority) {
      runTests();
      return;
    }
    if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
      Services.prefs.setBoolPref(
        "privacy.trackingprotection.annotate_channels",
        true
      );
      Services.prefs.setBoolPref(
        "privacy.trackingprotection.lower_network_priority",
        true
      );
    }
    testPriorityMap = [
      {
        path: normalOrigin + "/innocent.css",
        loadingPrincipal: null, // system principal
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
      {
        path: normalOrigin + "/innocent.js",
        loadingPrincipal: null, // system principal
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false, // ignored since tracking==false
      },
      {
        path: trackingOrigin + "/evil.css",
        loadingPrincipal: null, // system principal
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: false,
      },
      {
        path: trackingOrigin + "/evil.js",
        loadingPrincipal: null, // system principal
        topWindowURI: defaultTopWindowURI,
        expectedTracking: false,
        expectedPriority: Ci.nsISupportsPriority.PRIORITY_NORMAL,
        expectedThrottleable: true,
      },
    ];
    // We add the doPriorityTest test here so that it only gets injected in the
    // test list if we're not skipping over this test.
    tests.unshift(doPriorityTest);
    runTests();
  },

  function cleanUp() {
    httpServer.stop(do_test_finished);
    if (runtime.processType == runtime.PROCESS_TYPE_DEFAULT) {
      UrlClassifierTestUtils.cleanupTestTrackers();
    }
    runTests();
  },
];

function runTests() {
  if (!tests.length) {
    do_test_finished();
    return;
  }

  var test = tests.shift();
  test();
}

function run_test() {
  runTests();
  do_test_pending();
}
