summaryrefslogtreecommitdiffstats
path: root/browser/modules/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules/test/unit')
-rw-r--r--browser/modules/test/unit/test_E10SUtils_nested_URIs.js93
-rw-r--r--browser/modules/test/unit/test_HomePage.js91
-rw-r--r--browser/modules/test/unit/test_HomePage_ignore.js133
-rw-r--r--browser/modules/test/unit/test_LaterRun.js243
-rw-r--r--browser/modules/test/unit/test_PingCentre.js144
-rw-r--r--browser/modules/test/unit/test_ProfileCounter.js244
-rw-r--r--browser/modules/test/unit/test_Sanitizer_interrupted.js139
-rw-r--r--browser/modules/test/unit/test_SiteDataManager.js227
-rw-r--r--browser/modules/test/unit/test_SitePermissions.js393
-rw-r--r--browser/modules/test/unit/test_discovery.js138
-rw-r--r--browser/modules/test/unit/xpcshell.ini16
11 files changed, 1861 insertions, 0 deletions
diff --git a/browser/modules/test/unit/test_E10SUtils_nested_URIs.js b/browser/modules/test/unit/test_E10SUtils_nested_URIs.js
new file mode 100644
index 0000000000..b44739de03
--- /dev/null
+++ b/browser/modules/test/unit/test_E10SUtils_nested_URIs.js
@@ -0,0 +1,93 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+
+const { E10SUtils } = ChromeUtils.import(
+ "resource://gre/modules/E10SUtils.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var TEST_PREFERRED_REMOTE_TYPES = [
+ E10SUtils.WEB_REMOTE_TYPE,
+ E10SUtils.NOT_REMOTE,
+ "fakeRemoteType",
+];
+
+// These test cases give a nestedURL and a plainURL that should always load in
+// the same remote type. By making these tests comparisons, they should work
+// with any pref combination.
+var TEST_CASES = [
+ {
+ nestedURL: "jar:file:///some.file!/",
+ plainURL: "file:///some.file",
+ },
+ {
+ nestedURL: "jar:jar:file:///some.file!/!/",
+ plainURL: "file:///some.file",
+ },
+ {
+ nestedURL: "jar:http://some.site/file!/",
+ plainURL: "http://some.site/file",
+ },
+ {
+ nestedURL: "view-source:http://some.site",
+ plainURL: "http://some.site",
+ },
+ {
+ nestedURL: "view-source:file:///some.file",
+ plainURL: "file:///some.file",
+ },
+ {
+ nestedURL: "view-source:about:home",
+ plainURL: "about:home",
+ },
+ {
+ nestedURL: "view-source:about:robots",
+ plainURL: "about:robots",
+ },
+ {
+ nestedURL: "view-source:pcast:http://some.site",
+ plainURL: "http://some.site",
+ },
+];
+
+function run_test() {
+ for (let testCase of TEST_CASES) {
+ for (let preferredRemoteType of TEST_PREFERRED_REMOTE_TYPES) {
+ let plainUri = Services.io.newURI(testCase.plainURL);
+ let plainRemoteType = E10SUtils.getRemoteTypeForURIObject(
+ plainUri,
+ true,
+ false,
+ preferredRemoteType
+ );
+
+ let nestedUri = Services.io.newURI(testCase.nestedURL);
+ let nestedRemoteType = E10SUtils.getRemoteTypeForURIObject(
+ nestedUri,
+ true,
+ false,
+ preferredRemoteType
+ );
+
+ let nestedStr = nestedUri.scheme + ":";
+ do {
+ nestedUri = nestedUri.QueryInterface(Ci.nsINestedURI).innerURI;
+ if (nestedUri.scheme == "about") {
+ nestedStr += nestedUri.spec;
+ break;
+ }
+
+ nestedStr += nestedUri.scheme + ":";
+ } while (nestedUri instanceof Ci.nsINestedURI);
+
+ let plainStr =
+ plainUri.scheme == "about" ? plainUri.spec : plainUri.scheme + ":";
+ equal(
+ nestedRemoteType,
+ plainRemoteType,
+ `Check that ${nestedStr} loads in same remote type as ${plainStr}` +
+ ` with preferred remote type: ${preferredRemoteType}`
+ );
+ }
+ }
+}
diff --git a/browser/modules/test/unit/test_HomePage.js b/browser/modules/test/unit/test_HomePage.js
new file mode 100644
index 0000000000..cbe736233b
--- /dev/null
+++ b/browser/modules/test/unit/test_HomePage.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ HomePage: "resource:///modules/HomePage.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+ RemoteSettings: "resource://services-settings/remote-settings.js",
+ // RemoteSettingsClient: "resource://services-settings/RemoteSettingsClient.jsm",
+ sinon: "resource://testing-common/Sinon.jsm",
+});
+
+const HOMEPAGE_IGNORELIST = "homepage-urls";
+
+/**
+ * Provides a basic set of remote settings for use in tests.
+ */
+async function setupRemoteSettings() {
+ const settings = await RemoteSettings("hijack-blocklists");
+ sinon.stub(settings, "get").returns([
+ {
+ id: HOMEPAGE_IGNORELIST,
+ matches: ["ignore=me"],
+ _status: "synced",
+ },
+ ]);
+}
+
+add_task(async function setup() {
+ await setupRemoteSettings();
+});
+
+add_task(function test_HomePage() {
+ Assert.ok(
+ !HomePage.overridden,
+ "Homepage should not be overriden by default."
+ );
+ let newvalue = "about:blank|about:newtab";
+ HomePage.safeSet(newvalue);
+ Assert.ok(HomePage.overridden, "Homepage should be overriden after set()");
+ Assert.equal(HomePage.get(), newvalue, "Homepage should be ${newvalue}");
+ Assert.notEqual(
+ HomePage.getDefault(),
+ newvalue,
+ "Homepage should be ${newvalue}"
+ );
+ HomePage.reset();
+ Assert.ok(
+ !HomePage.overridden,
+ "Homepage should not be overriden by after reset."
+ );
+ Assert.equal(
+ HomePage.get(),
+ HomePage.getDefault(),
+ "Homepage and default should be equal after reset."
+ );
+});
+
+add_task(function test_readLocalizedHomepage() {
+ let newvalue = "data:text/plain,browser.startup.homepage%3Dabout%3Alocalized";
+ let complexvalue = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(
+ Ci.nsIPrefLocalizedString
+ );
+ complexvalue.data = newvalue;
+ Services.prefs
+ .getDefaultBranch(null)
+ .setComplexValue(
+ "browser.startup.homepage",
+ Ci.nsIPrefLocalizedString,
+ complexvalue
+ );
+ Assert.ok(!HomePage.overridden, "Complex value only works as default");
+ Assert.equal(HomePage.get(), "about:localized", "Get value from bundle");
+});
+
+add_task(function test_recoverEmptyHomepage() {
+ Assert.ok(
+ !HomePage.overridden,
+ "Homepage should not be overriden by default."
+ );
+ Services.prefs.setStringPref("browser.startup.homepage", "");
+ Assert.ok(HomePage.overridden, "Homepage is overriden with empty string.");
+ Assert.equal(HomePage.get(), HomePage.getDefault(), "Recover is default");
+ Assert.ok(!HomePage.overridden, "Recover should have set default");
+});
diff --git a/browser/modules/test/unit/test_HomePage_ignore.js b/browser/modules/test/unit/test_HomePage_ignore.js
new file mode 100644
index 0000000000..72ab0b0bd3
--- /dev/null
+++ b/browser/modules/test/unit/test_HomePage_ignore.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ HomePage: "resource:///modules/HomePage.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+ RemoteSettings: "resource://services-settings/remote-settings.js",
+ sinon: "resource://testing-common/Sinon.jsm",
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
+});
+
+const HOMEPAGE_IGNORELIST = "homepage-urls";
+
+/**
+ * Provides a basic set of remote settings for use in tests.
+ */
+async function setupRemoteSettings() {
+ const settings = await RemoteSettings("hijack-blocklists");
+ sinon.stub(settings, "get").returns([
+ {
+ id: HOMEPAGE_IGNORELIST,
+ matches: ["ignore=me", "ignoreCASE=ME"],
+ _status: "synced",
+ },
+ ]);
+}
+
+add_task(async function setup() {
+ await setupRemoteSettings();
+});
+
+add_task(async function test_initWithIgnoredPageCausesReset() {
+ // Set the preference direct as the set() would block us.
+ Services.prefs.setStringPref(
+ "browser.startup.homepage",
+ "http://bad/?ignore=me"
+ );
+ Assert.ok(HomePage.overridden, "Should have overriden the homepage");
+
+ await HomePage.delayedStartup();
+
+ Assert.ok(
+ !HomePage.overridden,
+ "Should no longer be overriding the homepage."
+ );
+ Assert.equal(
+ HomePage.get(),
+ HomePage.getDefault(),
+ "Should have reset to the default preference"
+ );
+
+ TelemetryTestUtils.assertEvents(
+ [{ object: "ignore", value: "saved_reset" }],
+ {
+ category: "homepage",
+ method: "preference",
+ }
+ );
+});
+
+add_task(async function test_updateIgnoreListCausesReset() {
+ Services.prefs.setStringPref(
+ "browser.startup.homepage",
+ "http://bad/?new=ignore"
+ );
+ Assert.ok(HomePage.overridden, "Should have overriden the homepage");
+
+ // Simulate an ignore list update.
+ await RemoteSettings("hijack-blocklists").emit("sync", {
+ data: {
+ current: [
+ {
+ id: HOMEPAGE_IGNORELIST,
+ schema: 1553857697843,
+ last_modified: 1553859483588,
+ matches: ["ignore=me", "ignoreCASE=ME", "new=ignore"],
+ },
+ ],
+ },
+ });
+
+ Assert.ok(
+ !HomePage.overridden,
+ "Should no longer be overriding the homepage."
+ );
+ Assert.equal(
+ HomePage.get(),
+ HomePage.getDefault(),
+ "Should have reset to the default preference"
+ );
+ TelemetryTestUtils.assertEvents(
+ [{ object: "ignore", value: "saved_reset" }],
+ {
+ category: "homepage",
+ method: "preference",
+ }
+ );
+});
+
+async function testSetIgnoredUrl(url) {
+ Assert.ok(!HomePage.overriden, "Should not be overriding the homepage");
+
+ await HomePage.set(url);
+
+ Assert.equal(
+ HomePage.get(),
+ HomePage.getDefault(),
+ "Should still have the default homepage."
+ );
+ Assert.ok(!HomePage.overriden, "Should not be overriding the homepage.");
+ TelemetryTestUtils.assertEvents(
+ [{ object: "ignore", value: "set_blocked" }],
+ {
+ category: "homepage",
+ method: "preference",
+ }
+ );
+}
+
+add_task(async function test_setIgnoredUrl() {
+ await testSetIgnoredUrl("http://bad/?ignore=me");
+});
+
+add_task(async function test_setIgnoredUrl_case() {
+ await testSetIgnoredUrl("http://bad/?Ignorecase=me");
+});
diff --git a/browser/modules/test/unit/test_LaterRun.js b/browser/modules/test/unit/test_LaterRun.js
new file mode 100644
index 0000000000..580d6945eb
--- /dev/null
+++ b/browser/modules/test/unit/test_LaterRun.js
@@ -0,0 +1,243 @@
+"use strict";
+
+const kEnabledPref = "browser.laterrun.enabled";
+const kPagePrefRoot = "browser.laterrun.pages.";
+const kSessionCountPref = "browser.laterrun.bookkeeping.sessionCount";
+const kProfileCreationTime = "browser.laterrun.bookkeeping.profileCreationTime";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { LaterRun } = ChromeUtils.import("resource:///modules/LaterRun.jsm");
+
+Services.prefs.setBoolPref(kEnabledPref, true);
+const { updateAppInfo } = ChromeUtils.import(
+ "resource://testing-common/AppInfo.jsm"
+);
+updateAppInfo();
+
+add_task(async function test_page_applies() {
+ Services.prefs.setCharPref(
+ kPagePrefRoot + "test_LaterRun_unittest.url",
+ "https://www.mozilla.org/%VENDOR%/%NAME%/%ID%/%VERSION%/"
+ );
+ Services.prefs.setIntPref(
+ kPagePrefRoot + "test_LaterRun_unittest.minimumHoursSinceInstall",
+ 10
+ );
+ Services.prefs.setIntPref(
+ kPagePrefRoot + "test_LaterRun_unittest.minimumSessionCount",
+ 3
+ );
+
+ let pages = LaterRun.readPages();
+ // We have to filter the pages because it's possible Firefox ships with other URLs
+ // that get included in this test.
+ pages = pages.filter(
+ page => page.pref == kPagePrefRoot + "test_LaterRun_unittest."
+ );
+ Assert.equal(pages.length, 1, "Got 1 page");
+ let page = pages[0];
+ Assert.equal(
+ page.pref,
+ kPagePrefRoot + "test_LaterRun_unittest.",
+ "Should know its own pref"
+ );
+ Assert.equal(
+ page.minimumHoursSinceInstall,
+ 10,
+ "Needs to have 10 hours since install"
+ );
+ Assert.equal(page.minimumSessionCount, 3, "Needs to have 3 sessions");
+ Assert.equal(page.requireBoth, false, "Either requirement is enough");
+ let expectedURL =
+ "https://www.mozilla.org/" +
+ Services.appinfo.vendor +
+ "/" +
+ Services.appinfo.name +
+ "/" +
+ Services.appinfo.ID +
+ "/" +
+ Services.appinfo.version +
+ "/";
+ Assert.equal(page.url, expectedURL, "URL is stored correctly");
+
+ Assert.ok(
+ page.applies({ hoursSinceInstall: 1, sessionCount: 3 }),
+ "Applies when session count has been met."
+ );
+ Assert.ok(
+ page.applies({ hoursSinceInstall: 1, sessionCount: 4 }),
+ "Applies when session count has been exceeded."
+ );
+ Assert.ok(
+ page.applies({ hoursSinceInstall: 10, sessionCount: 2 }),
+ "Applies when total session time has been met."
+ );
+ Assert.ok(
+ page.applies({ hoursSinceInstall: 20, sessionCount: 2 }),
+ "Applies when total session time has been exceeded."
+ );
+ Assert.ok(
+ page.applies({ hoursSinceInstall: 10, sessionCount: 3 }),
+ "Applies when both time and session count have been met."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 1, sessionCount: 1 }),
+ "Does not apply when neither time and session count have been met."
+ );
+
+ page.requireBoth = true;
+
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 1, sessionCount: 3 }),
+ "Does not apply when only session count has been met."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 1, sessionCount: 4 }),
+ "Does not apply when only session count has been exceeded."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 10, sessionCount: 2 }),
+ "Does not apply when only total session time has been met."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 20, sessionCount: 2 }),
+ "Does not apply when only total session time has been exceeded."
+ );
+ Assert.ok(
+ page.applies({ hoursSinceInstall: 10, sessionCount: 3 }),
+ "Applies when both time and session count have been met."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 1, sessionCount: 1 }),
+ "Does not apply when neither time and session count have been met."
+ );
+
+ // Check that pages that have run never apply:
+ Services.prefs.setBoolPref(
+ kPagePrefRoot + "test_LaterRun_unittest.hasRun",
+ true
+ );
+ page.requireBoth = false;
+
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 1, sessionCount: 3 }),
+ "Does not apply when page has already run (sessionCount equal)."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 1, sessionCount: 4 }),
+ "Does not apply when page has already run (sessionCount exceeding)."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 10, sessionCount: 2 }),
+ "Does not apply when page has already run (hoursSinceInstall equal)."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 20, sessionCount: 2 }),
+ "Does not apply when page has already run (hoursSinceInstall exceeding)."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 10, sessionCount: 3 }),
+ "Does not apply when page has already run (both criteria equal)."
+ );
+ Assert.ok(
+ !page.applies({ hoursSinceInstall: 1, sessionCount: 1 }),
+ "Does not apply when page has already run (both criteria insufficient anyway)."
+ );
+
+ clearAllPagePrefs();
+});
+
+add_task(async function test_get_URL() {
+ Services.prefs.setIntPref(
+ kProfileCreationTime,
+ Math.floor((Date.now() - 11 * 60 * 60 * 1000) / 1000)
+ );
+ Services.prefs.setCharPref(
+ kPagePrefRoot + "test_LaterRun_unittest.url",
+ "https://www.mozilla.org/"
+ );
+ Services.prefs.setIntPref(
+ kPagePrefRoot + "test_LaterRun_unittest.minimumHoursSinceInstall",
+ 10
+ );
+ Services.prefs.setIntPref(
+ kPagePrefRoot + "test_LaterRun_unittest.minimumSessionCount",
+ 3
+ );
+ let pages = LaterRun.readPages();
+ // We have to filter the pages because it's possible Firefox ships with other URLs
+ // that get included in this test.
+ pages = pages.filter(
+ page => page.pref == kPagePrefRoot + "test_LaterRun_unittest."
+ );
+ Assert.equal(pages.length, 1, "Should only be 1 matching page");
+ let page = pages[0];
+ let url;
+ do {
+ url = LaterRun.getURL();
+ // We have to loop because it's possible Firefox ships with other URLs that get triggered by
+ // this test.
+ } while (url && url != "https://www.mozilla.org/");
+ Assert.equal(
+ url,
+ "https://www.mozilla.org/",
+ "URL should be as expected when prefs are set."
+ );
+ Assert.ok(
+ Services.prefs.prefHasUserValue(
+ kPagePrefRoot + "test_LaterRun_unittest.hasRun"
+ ),
+ "Should have set pref"
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref(kPagePrefRoot + "test_LaterRun_unittest.hasRun"),
+ "Should have set pref to true"
+ );
+ Assert.ok(page.hasRun, "Other page objects should know it has run, too.");
+
+ clearAllPagePrefs();
+});
+
+add_task(async function test_insecure_urls() {
+ Services.prefs.setCharPref(
+ kPagePrefRoot + "test_LaterRun_unittest.url",
+ "http://www.mozilla.org/"
+ );
+ Services.prefs.setIntPref(
+ kPagePrefRoot + "test_LaterRun_unittest.minimumHoursSinceInstall",
+ 10
+ );
+ Services.prefs.setIntPref(
+ kPagePrefRoot + "test_LaterRun_unittest.minimumSessionCount",
+ 3
+ );
+ let pages = LaterRun.readPages();
+ // We have to filter the pages because it's possible Firefox ships with other URLs
+ // that get triggered in this test.
+ pages = pages.filter(
+ page => page.pref == kPagePrefRoot + "test_LaterRun_unittest."
+ );
+ Assert.equal(pages.length, 0, "URL with non-https scheme should get ignored");
+ clearAllPagePrefs();
+});
+
+add_task(async function test_dynamic_pref_getter_setter() {
+ delete LaterRun._sessionCount;
+ Services.prefs.setIntPref(kSessionCountPref, 0);
+ Assert.equal(LaterRun.sessionCount, 0, "Should start at 0");
+
+ LaterRun.sessionCount++;
+ Assert.equal(LaterRun.sessionCount, 1, "Should increment.");
+ Assert.equal(
+ Services.prefs.getIntPref(kSessionCountPref),
+ 1,
+ "Should update pref"
+ );
+});
+
+function clearAllPagePrefs() {
+ let allChangedPrefs = Services.prefs.getChildList(kPagePrefRoot);
+ for (let pref of allChangedPrefs) {
+ Services.prefs.clearUserPref(pref);
+ }
+}
diff --git a/browser/modules/test/unit/test_PingCentre.js b/browser/modules/test/unit/test_PingCentre.js
new file mode 100644
index 0000000000..2fc6f4a2c7
--- /dev/null
+++ b/browser/modules/test/unit/test_PingCentre.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { PingCentre, PingCentreConstants } = ChromeUtils.import(
+ "resource:///modules/PingCentre.jsm"
+);
+const { TelemetryEnvironment } = ChromeUtils.import(
+ "resource://gre/modules/TelemetryEnvironment.jsm"
+);
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+const { UpdateUtils } = ChromeUtils.import(
+ "resource://gre/modules/UpdateUtils.jsm"
+);
+
+const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
+
+const FAKE_PING = { event: "fake_event", value: "fake_value", locale: "en-US" };
+const FAKE_ENDPOINT = "https://www.test.com";
+
+let pingCentre;
+let sandbox;
+
+function _setUp() {
+ Services.prefs.setBoolPref(PingCentreConstants.TELEMETRY_PREF, true);
+ Services.prefs.setBoolPref(PingCentreConstants.FHR_UPLOAD_ENABLED_PREF, true);
+ Services.prefs.setBoolPref(PingCentreConstants.LOGGING_PREF, true);
+ sandbox.restore();
+}
+
+add_task(function setup() {
+ sandbox = sinon.createSandbox();
+ _setUp();
+ pingCentre = new PingCentre({ topic: "test_topic" });
+
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ Services.prefs.clearUserPref(PingCentreConstants.TELEMETRY_PREF);
+ Services.prefs.clearUserPref(PingCentreConstants.FHR_UPLOAD_ENABLED_PREF);
+ Services.prefs.clearUserPref(PingCentreConstants.LOGGING_PREF);
+ });
+});
+
+add_task(function test_enabled() {
+ _setUp();
+ Assert.ok(pingCentre.enabled, "Telemetry should be on");
+});
+
+add_task(function test_disabled_by_pingCentre() {
+ _setUp();
+ Services.prefs.setBoolPref(PingCentreConstants.TELEMETRY_PREF, false);
+
+ Assert.ok(!pingCentre.enabled, "Telemetry should be off");
+});
+
+add_task(function test_disabled_by_FirefoxHealthReport() {
+ _setUp();
+ Services.prefs.setBoolPref(
+ PingCentreConstants.FHR_UPLOAD_ENABLED_PREF,
+ false
+ );
+
+ Assert.ok(!pingCentre.enabled, "Telemetry should be off");
+});
+
+add_task(function test_logging() {
+ _setUp();
+ Assert.ok(pingCentre.logging, "Logging should be on");
+
+ Services.prefs.setBoolPref(PingCentreConstants.LOGGING_PREF, false);
+
+ Assert.ok(!pingCentre.logging, "Logging should be off");
+});
+
+add_task(function test_createExperimentsPayload() {
+ _setUp();
+ const activeExperiments = {
+ exp1: { branch: "foo", enrollmentID: "SOME_RANDON_ID" },
+ exp2: { branch: "bar", type: "PrefStudy" },
+ exp3: {},
+ };
+ sandbox
+ .stub(TelemetryEnvironment, "getActiveExperiments")
+ .returns(activeExperiments);
+ const expected = {
+ exp1: { branch: "foo" },
+ exp2: { branch: "bar" },
+ };
+
+ const experiments = pingCentre._createExperimentsPayload();
+
+ Assert.deepEqual(
+ experiments,
+ expected,
+ "Should create experiments with all the required context"
+ );
+});
+
+add_task(function test_createExperimentsPayload_without_active_experiments() {
+ _setUp();
+ sandbox.stub(TelemetryEnvironment, "getActiveExperiments").returns({});
+ const experiments = pingCentre._createExperimentsPayload({});
+
+ Assert.deepEqual(experiments, {}, "Should send an empty object");
+});
+
+add_task(function test_createStructuredIngestionPing() {
+ _setUp();
+ sandbox
+ .stub(TelemetryEnvironment, "getActiveExperiments")
+ .returns({ exp1: { branch: "foo" } });
+ const ping = pingCentre._createStructuredIngestionPing(FAKE_PING);
+ const expected = {
+ experiments: { exp1: { branch: "foo" } },
+ locale: "en-US",
+ version: AppConstants.MOZ_APP_VERSION,
+ release_channel: UpdateUtils.getUpdateChannel(false),
+ ...FAKE_PING,
+ };
+
+ Assert.deepEqual(ping, expected, "Should create a valid ping");
+});
+
+add_task(function test_sendStructuredIngestionPing_disabled() {
+ _setUp();
+ sandbox.stub(PingCentre, "_sendInGzip").resolves();
+ Services.prefs.setBoolPref(PingCentreConstants.TELEMETRY_PREF, false);
+ pingCentre.sendStructuredIngestionPing(FAKE_PING, FAKE_ENDPOINT);
+
+ Assert.ok(PingCentre._sendInGzip.notCalled, "Should not be sent");
+});
+
+add_task(function test_sendStructuredIngestionPing_success() {
+ _setUp();
+ sandbox.stub(PingCentre, "_sendInGzip").resolves();
+ pingCentre.sendStructuredIngestionPing(FAKE_PING, FAKE_ENDPOINT);
+
+ Assert.equal(PingCentre._sendInGzip.callCount, 1, "Should be sent");
+});
diff --git a/browser/modules/test/unit/test_ProfileCounter.js b/browser/modules/test/unit/test_ProfileCounter.js
new file mode 100644
index 0000000000..d24977b60f
--- /dev/null
+++ b/browser/modules/test/unit/test_ProfileCounter.js
@@ -0,0 +1,244 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const { BrowserUsageTelemetry } = ChromeUtils.import(
+ "resource:///modules/BrowserUsageTelemetry.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { TelemetryTestUtils } = ChromeUtils.import(
+ "resource://testing-common/TelemetryTestUtils.jsm"
+);
+
+const PROFILE_COUNT_SCALAR = "browser.engagement.profile_count";
+// Largest possible uint32_t value represents an error.
+const SCALAR_ERROR_VALUE = 0;
+
+const FILE_OPEN_OPERATION = "open";
+const ERROR_FILE_NOT_FOUND = "NotFoundError";
+const ERROR_ACCESS_DENIED = "NotAllowedError";
+
+// We will redirect I/O to/from the profile counter file to read/write this
+// variable instead. That makes it easier for us to:
+// - avoid interference from any pre-existing file
+// - read and change the values in the file.
+// - clean up changes made to the file
+// We will translate a null value stored here to a File Not Found error.
+var gFakeProfileCounterFile = null;
+// We will use this to check that the profile counter code doesn't try to write
+// to multiple files (since this test will malfunction in that case due to
+// gFakeProfileCounterFile only being setup to accommodate a single file).
+var gProfileCounterFilePath = null;
+
+// Storing a value here lets us test the behavior when we encounter an error
+// reading or writing to the file. A null value means that no error will
+// be simulated (other than possibly a NotFoundError).
+var gNextReadExceptionReason = null;
+var gNextWriteExceptionReason = null;
+
+// Nothing will actually be stored in this directory, so it's not important that
+// it be valid, but the leafname should be unique to this test in order to be
+// sure of preventing name conflicts with the pref:
+// `browser.engagement.profileCounted.${hash}`
+function getDummyUpdateDirectory() {
+ const testName = "test_ProfileCounter";
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath(`C:\\foo\\bar\\${testName}`);
+ return dir;
+}
+
+// We aren't going to bother generating anything looking like a real client ID
+// for this. The only real requirements for client ids is that they not repeat
+// and that they be strings. So we'll just return an integer as a string and
+// increment it when we want a new client id.
+var gDummyTelemetryClientId = 0;
+function getDummyTelemetryClientId() {
+ return gDummyTelemetryClientId.toString();
+}
+function setNewDummyTelemetryClientId() {
+ ++gDummyTelemetryClientId;
+}
+
+// This function clears the pref that prevents this profile from being counted
+// more than once per installation.
+function clearProfileCountedPref() {
+ const updateDirectory = getDummyUpdateDirectory();
+ const hash = updateDirectory.leafName;
+ const prefName = `browser.engagement.profileCounted.${hash}`;
+ Services.prefs.clearUserPref(prefName);
+}
+
+// Returns null if the (fake) profile count file hasn't been created yet.
+function getProfileCount() {
+ // Strict equality to ensure distinguish properly between a non-existent
+ // file and an empty one.
+ if (gFakeProfileCounterFile === null) {
+ return null;
+ }
+ let saveData = JSON.parse(gFakeProfileCounterFile);
+ return saveData.profileTelemetryIds.length;
+}
+
+// Resets the state to the original state, before the profile count file has
+// even been written.
+// If resetFile is specified as false, this will reset everything except for the
+// file itself. This allows us to sort of pretend that another installation
+// wrote the file.
+function reset(resetFile = true) {
+ if (resetFile) {
+ gFakeProfileCounterFile = null;
+ }
+ gNextReadExceptionReason = null;
+ gNextWriteExceptionReason = null;
+ setNewDummyTelemetryClientId();
+}
+
+function setup() {
+ reset();
+
+ BrowserUsageTelemetry.Policy.readProfileCountFile = async path => {
+ if (!gProfileCounterFilePath) {
+ gProfileCounterFilePath = path;
+ } else {
+ // We've only got one mock-file variable. Make sure we are always
+ // accessing the same file or this will cause problems.
+ Assert.equal(
+ gProfileCounterFilePath,
+ path,
+ "Only one file should be accessed"
+ );
+ }
+ // Strict equality to ensure distinguish properly between null and 0.
+ if (gNextReadExceptionReason !== null) {
+ let ex = new DOMException(FILE_OPEN_OPERATION, gNextReadExceptionReason);
+ gNextReadExceptionReason = null;
+ throw ex;
+ }
+ // Strict equality to ensure distinguish properly between a non-existent
+ // file and an empty one.
+ if (gFakeProfileCounterFile === null) {
+ throw new DOMException(FILE_OPEN_OPERATION, ERROR_FILE_NOT_FOUND);
+ }
+ return gFakeProfileCounterFile;
+ };
+ BrowserUsageTelemetry.Policy.writeProfileCountFile = async (path, data) => {
+ if (!gProfileCounterFilePath) {
+ gProfileCounterFilePath = path;
+ } else {
+ // We've only got one mock-file variable. Make sure we are always
+ // accessing the same file or this will cause problems.
+ Assert.equal(
+ gProfileCounterFilePath,
+ path,
+ "Only one file should be accessed"
+ );
+ }
+ // Strict equality to ensure distinguish properly between null and 0.
+ if (gNextWriteExceptionReason !== null) {
+ let ex = new DOMException(FILE_OPEN_OPERATION, gNextWriteExceptionReason);
+ gNextWriteExceptionReason = null;
+ throw ex;
+ }
+ gFakeProfileCounterFile = data;
+ };
+ BrowserUsageTelemetry.Policy.getUpdateDirectory = getDummyUpdateDirectory;
+ BrowserUsageTelemetry.Policy.getTelemetryClientId = getDummyTelemetryClientId;
+}
+
+// Checks that the number of profiles reported is the number expected. Because
+// of bucketing, the raw count may be different than the reported count.
+function checkSuccess(profilesReported, rawCount = profilesReported) {
+ Assert.equal(rawCount, getProfileCount());
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ PROFILE_COUNT_SCALAR,
+ profilesReported,
+ "The value reported to telemetry should be the expected profile count"
+ );
+}
+
+function checkError() {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ // Bug 1633883 - SCALAR_ERROR_VALUE is 0, but currently, assertScalar, if
+ // given an expected value of 0, will check that the scalar is
+ // unset rather than checking that its value is 0.
+ // If this is changed, the check below can be replaced with a
+ // TelemetryTestUtils.assertScalar call.
+ Assert.ok(
+ PROFILE_COUNT_SCALAR in scalars &&
+ scalars[PROFILE_COUNT_SCALAR] === SCALAR_ERROR_VALUE,
+ "The value reported to telemetry should be the error value"
+ );
+}
+
+add_task(async function testProfileCounter() {
+ setup();
+
+ info("Testing basic functionality, single install");
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkSuccess(1);
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkSuccess(1);
+
+ // Fake another installation by resetting everything except for the profile
+ // count file.
+ reset(false);
+
+ info("Testing basic functionality, faking a second install");
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkSuccess(2);
+
+ // Check if we properly handle the case where we cannot read from the file
+ // and we have already set its contents. This should report an error.
+ info("Testing read error after successful write");
+ gNextReadExceptionReason = ERROR_ACCESS_DENIED;
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkError();
+
+ reset();
+
+ // A read error should cause an error to be reported, but should also write
+ // to the file in an attempt to fix it. So the next (successful) read should
+ // result in the correct telemetry.
+ info("Testing read error self-correction");
+ gNextReadExceptionReason = ERROR_ACCESS_DENIED;
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkError();
+
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkSuccess(1);
+
+ reset();
+
+ // If the file is malformed. We should report an error and fix it, then report
+ // the correct profile count next time.
+ info("Testing with malformed profile count file");
+ gFakeProfileCounterFile = "<malformed file data>";
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkError();
+
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkSuccess(1);
+
+ reset();
+
+ // If we haven't yet written to the file, a write error should cause an error
+ // to be reported.
+ info("Testing write error before the first write");
+ gNextWriteExceptionReason = ERROR_ACCESS_DENIED;
+ await BrowserUsageTelemetry.reportProfileCount();
+ checkError();
+
+ reset();
+
+ info("Testing bucketing");
+ // Fake 15 installations to drive the raw profile count up to 15.
+ for (let i = 0; i < 15; i++) {
+ reset(false);
+ await BrowserUsageTelemetry.reportProfileCount();
+ }
+ // With bucketing, values from 10-99 should all be reported as 10.
+ checkSuccess(10, 15);
+});
diff --git a/browser/modules/test/unit/test_Sanitizer_interrupted.js b/browser/modules/test/unit/test_Sanitizer_interrupted.js
new file mode 100644
index 0000000000..fd80a3af77
--- /dev/null
+++ b/browser/modules/test/unit/test_Sanitizer_interrupted.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+do_get_profile();
+
+// Test that interrupted sanitizations are properly tracked.
+
+add_task(async function() {
+ const { Sanitizer } = ChromeUtils.import("resource:///modules/Sanitizer.jsm");
+
+ Services.prefs.setBoolPref(Sanitizer.PREF_NEWTAB_SEGREGATION, false);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN);
+ Services.prefs.clearUserPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata");
+ Services.prefs.clearUserPref(Sanitizer.PREF_NEWTAB_SEGREGATION);
+ });
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata", true);
+
+ await Sanitizer.onStartup();
+ Assert.ok(Sanitizer.shouldSanitizeOnShutdown, "Should sanitize on shutdown");
+
+ let pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
+ );
+ Assert.equal(
+ pendingSanitizations.length,
+ 1,
+ "Should have 1 pending sanitization"
+ );
+ Assert.equal(
+ pendingSanitizations[0].id,
+ "shutdown",
+ "Should be the shutdown sanitization"
+ );
+ Assert.ok(
+ pendingSanitizations[0].itemsToClear.includes("formdata"),
+ "Pref has been setup"
+ );
+ Assert.ok(
+ !pendingSanitizations[0].options.isShutdown,
+ "Shutdown option is not present"
+ );
+
+ // Check the preference listeners.
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false);
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
+ );
+ Assert.equal(
+ pendingSanitizations.length,
+ 0,
+ "Should not have pending sanitizations"
+ );
+ Assert.ok(
+ !Sanitizer.shouldSanitizeOnShutdown,
+ "Should not sanitize on shutdown"
+ );
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, true);
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
+ );
+ Assert.equal(
+ pendingSanitizations.length,
+ 1,
+ "Should have 1 pending sanitization"
+ );
+ Assert.equal(
+ pendingSanitizations[0].id,
+ "shutdown",
+ "Should be the shutdown sanitization"
+ );
+
+ Assert.ok(
+ pendingSanitizations[0].itemsToClear.includes("formdata"),
+ "Pending sanitizations should include formdata"
+ );
+ Services.prefs.setBoolPref(
+ Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata",
+ false
+ );
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
+ );
+ Assert.equal(
+ pendingSanitizations.length,
+ 1,
+ "Should have 1 pending sanitization"
+ );
+ Assert.ok(
+ !pendingSanitizations[0].itemsToClear.includes("formdata"),
+ "Pending sanitizations should have been updated"
+ );
+
+ // Check a sanitization properly rebuilds the pref.
+ await Sanitizer.sanitize(["formdata"]);
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
+ );
+ Assert.equal(
+ pendingSanitizations.length,
+ 1,
+ "Should have 1 pending sanitization"
+ );
+ Assert.equal(
+ pendingSanitizations[0].id,
+ "shutdown",
+ "Should be the shutdown sanitization"
+ );
+
+ // Startup should run the pending one and setup a new shutdown sanitization.
+ Services.prefs.setBoolPref(
+ Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata",
+ false
+ );
+ await Sanitizer.onStartup();
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
+ );
+ Assert.equal(
+ pendingSanitizations.length,
+ 1,
+ "Should have 1 pending sanitization"
+ );
+ Assert.equal(
+ pendingSanitizations[0].id,
+ "shutdown",
+ "Should be the shutdown sanitization"
+ );
+ Assert.ok(
+ !pendingSanitizations[0].itemsToClear.includes("formdata"),
+ "Pref has been setup"
+ );
+});
diff --git a/browser/modules/test/unit/test_SiteDataManager.js b/browser/modules/test/unit/test_SiteDataManager.js
new file mode 100644
index 0000000000..5e2ec254b0
--- /dev/null
+++ b/browser/modules/test/unit/test_SiteDataManager.js
@@ -0,0 +1,227 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const EXAMPLE_ORIGIN = "https://www.example.com";
+const EXAMPLE_ORIGIN_2 = "https://example.org";
+const EXAMPLE_ORIGIN_3 = "http://localhost:8000";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { SiteDataManager } = ChromeUtils.import(
+ "resource:///modules/SiteDataManager.jsm"
+);
+const { SiteDataTestUtils } = ChromeUtils.import(
+ "resource://testing-common/SiteDataTestUtils.jsm"
+);
+const { PermissionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PermissionTestUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "setTimeout",
+ "resource://gre/modules/Timer.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "TestUtils",
+ "resource://testing-common/TestUtils.jsm"
+);
+
+add_task(function setup() {
+ do_get_profile();
+});
+
+add_task(async function testGetSites() {
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo1", "bar1");
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo2", "bar2");
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN_2, "foo", "bar");
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
+ await SiteDataTestUtils.persist(EXAMPLE_ORIGIN_2);
+
+ await SiteDataManager.updateSites();
+
+ let sites = await SiteDataManager.getSites();
+
+ let site1 = sites.find(site => site.baseDomain == "example.com");
+ let site2 = sites.find(site => site.baseDomain == "example.org");
+
+ Assert.equal(
+ site1.baseDomain,
+ "example.com",
+ "Has the correct base domain for example.com"
+ );
+ Assert.equal(
+ site1.host,
+ "www.example.com",
+ "Has the correct host for example.com"
+ );
+ Assert.greater(site1.usage, 4096, "Has correct usage for example.com");
+ Assert.equal(site1.persisted, false, "example.com is not persisted");
+ Assert.equal(
+ site1.cookies.length,
+ 2,
+ "Has correct number of cookies for example.com"
+ );
+ Assert.ok(
+ typeof site1.lastAccessed.getDate == "function",
+ "lastAccessed for example.com is a Date"
+ );
+ Assert.ok(
+ site1.lastAccessed > Date.now() - 60 * 1000,
+ "lastAccessed for example.com happened recently"
+ );
+
+ Assert.equal(
+ site2.baseDomain,
+ "example.org",
+ "Has the correct base domain for example.org"
+ );
+ Assert.equal(
+ site2.host,
+ "example.org",
+ "Has the correct host for example.org"
+ );
+ Assert.greater(site2.usage, 2048, "Has correct usage for example.org");
+ Assert.equal(site2.persisted, true, "example.org is persisted");
+ Assert.equal(
+ site2.cookies.length,
+ 1,
+ "Has correct number of cookies for example.org"
+ );
+ Assert.ok(
+ typeof site2.lastAccessed.getDate == "function",
+ "lastAccessed for example.org is a Date"
+ );
+ Assert.ok(
+ site2.lastAccessed > Date.now() - 60 * 1000,
+ "lastAccessed for example.org happened recently"
+ );
+
+ await SiteDataTestUtils.clear();
+});
+
+add_task(async function testGetTotalUsage() {
+ await SiteDataManager.updateSites();
+ let sites = await SiteDataManager.getSites();
+ Assert.equal(sites.length, 0, "SiteDataManager is empty");
+
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
+
+ await SiteDataManager.updateSites();
+
+ let usage = await SiteDataManager.getTotalUsage();
+
+ Assert.greater(usage, 4096 + 2048, "Has the correct total usage.");
+
+ await SiteDataTestUtils.clear();
+});
+
+add_task(async function testRemove() {
+ await SiteDataManager.updateSites();
+
+ let uri = Services.io.newURI(EXAMPLE_ORIGIN);
+ PermissionTestUtils.add(uri, "camera", Services.perms.ALLOW_ACTION);
+
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo1", "bar1");
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo2", "bar2");
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN_2, "foo", "bar");
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
+ await SiteDataTestUtils.persist(EXAMPLE_ORIGIN_2);
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_3, 2048);
+
+ await SiteDataManager.updateSites();
+
+ let sites = await SiteDataManager.getSites();
+
+ Assert.equal(sites.length, 3, "Has three sites.");
+
+ await SiteDataManager.remove(["localhost"]);
+
+ sites = await SiteDataManager.getSites();
+
+ Assert.equal(sites.length, 2, "Has two sites.");
+
+ await SiteDataManager.remove(["www.example.com"]);
+
+ sites = await SiteDataManager.getSites();
+
+ Assert.equal(sites.length, 1, "Has one site.");
+ Assert.equal(
+ sites[0].host,
+ "example.org",
+ "Has not cleared data for example.org"
+ );
+
+ let usage = await SiteDataTestUtils.getQuotaUsage(EXAMPLE_ORIGIN);
+ Assert.equal(usage, 0, "Has cleared quota usage for example.com");
+
+ let cookies = Services.cookies.countCookiesFromHost("example.com");
+ Assert.equal(cookies, 0, "Has cleared cookies for example.com");
+
+ let perm = PermissionTestUtils.testPermission(uri, "persistent-storage");
+ Assert.equal(
+ perm,
+ Services.perms.UNKNOWN_ACTION,
+ "Cleared the persistent-storage permission."
+ );
+ perm = PermissionTestUtils.testPermission(uri, "camera");
+ Assert.equal(
+ perm,
+ Services.perms.ALLOW_ACTION,
+ "Did not clear other permissions."
+ );
+
+ PermissionTestUtils.remove(uri, "camera");
+});
+
+add_task(async function testRemoveSiteData() {
+ let uri = Services.io.newURI(EXAMPLE_ORIGIN);
+ PermissionTestUtils.add(uri, "camera", Services.perms.ALLOW_ACTION);
+
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo1", "bar1");
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo2", "bar2");
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
+ SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN_2, "foo", "bar");
+ await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
+ await SiteDataTestUtils.persist(EXAMPLE_ORIGIN_2);
+
+ await SiteDataManager.updateSites();
+
+ let sites = await SiteDataManager.getSites();
+
+ Assert.equal(sites.length, 2, "Has two sites.");
+
+ await SiteDataManager.removeSiteData();
+
+ sites = await SiteDataManager.getSites();
+
+ Assert.equal(sites.length, 0, "Has no sites.");
+
+ let usage = await SiteDataTestUtils.getQuotaUsage(EXAMPLE_ORIGIN);
+ Assert.equal(usage, 0, "Has cleared quota usage for example.com");
+
+ usage = await SiteDataTestUtils.getQuotaUsage(EXAMPLE_ORIGIN_2);
+ Assert.equal(usage, 0, "Has cleared quota usage for example.org");
+
+ let cookies = Services.cookies.countCookiesFromHost("example.org");
+ Assert.equal(cookies, 0, "Has cleared cookies for example.org");
+
+ let perm = PermissionTestUtils.testPermission(uri, "persistent-storage");
+ Assert.equal(
+ perm,
+ Services.perms.UNKNOWN_ACTION,
+ "Cleared the persistent-storage permission."
+ );
+ perm = PermissionTestUtils.testPermission(uri, "camera");
+ Assert.equal(
+ perm,
+ Services.perms.ALLOW_ACTION,
+ "Did not clear other permissions."
+ );
+
+ PermissionTestUtils.remove(uri, "camera");
+});
diff --git a/browser/modules/test/unit/test_SitePermissions.js b/browser/modules/test/unit/test_SitePermissions.js
new file mode 100644
index 0000000000..429fa18765
--- /dev/null
+++ b/browser/modules/test/unit/test_SitePermissions.js
@@ -0,0 +1,393 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const { SitePermissions } = ChromeUtils.import(
+ "resource:///modules/SitePermissions.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const RESIST_FINGERPRINTING_ENABLED = Services.prefs.getBoolPref(
+ "privacy.resistFingerprinting"
+);
+const MIDI_ENABLED = Services.prefs.getBoolPref("dom.webmidi.enabled");
+
+const EXT_PROTOCOL_ENABLED = Services.prefs.getBoolPref(
+ "security.external_protocol_requires_permission"
+);
+
+const STATE_PARTITIONING_ENABLED = Services.prefs.getBoolPref(
+ "browser.contentblocking.state-partitioning.mvp.ui.enabled"
+);
+
+add_task(async function testPermissionsListing() {
+ let expectedPermissions = [
+ "autoplay-media",
+ "camera",
+ "cookie",
+ "desktop-notification",
+ "focus-tab-by-prompt",
+ "geo",
+ "install",
+ "microphone",
+ "popup",
+ "screen",
+ "shortcuts",
+ "persistent-storage",
+ "storage-access",
+ "xr",
+ ];
+ if (RESIST_FINGERPRINTING_ENABLED) {
+ // Canvas permission should be hidden unless privacy.resistFingerprinting
+ // is true.
+ expectedPermissions.push("canvas");
+ }
+ if (MIDI_ENABLED) {
+ // Should remove this checking and add it as default after it is fully pref'd-on.
+ expectedPermissions.push("midi");
+ expectedPermissions.push("midi-sysex");
+ }
+ if (EXT_PROTOCOL_ENABLED) {
+ expectedPermissions.push("open-protocol-handler");
+ }
+ if (STATE_PARTITIONING_ENABLED) {
+ expectedPermissions.push("3rdPartyStorage");
+ }
+ Assert.deepEqual(
+ SitePermissions.listPermissions().sort(),
+ expectedPermissions.sort(),
+ "Correct list of all permissions"
+ );
+});
+
+add_task(async function testGetAllByPrincipal() {
+ // check that it returns an empty array on an invalid principal
+ // like a principal with an about URI, which doesn't support site permissions
+ let wrongPrincipal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "about:config"
+ );
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(wrongPrincipal), []);
+
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://example.com"
+ );
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []);
+
+ SitePermissions.setForPrincipal(principal, "camera", SitePermissions.ALLOW);
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [
+ {
+ id: "camera",
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ ]);
+
+ SitePermissions.setForPrincipal(
+ principal,
+ "microphone",
+ SitePermissions.ALLOW,
+ SitePermissions.SCOPE_SESSION
+ );
+ SitePermissions.setForPrincipal(
+ principal,
+ "desktop-notification",
+ SitePermissions.BLOCK
+ );
+
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [
+ {
+ id: "camera",
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ {
+ id: "microphone",
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_SESSION,
+ },
+ {
+ id: "desktop-notification",
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ ]);
+
+ SitePermissions.removeFromPrincipal(principal, "microphone");
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [
+ {
+ id: "camera",
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ {
+ id: "desktop-notification",
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ ]);
+
+ SitePermissions.removeFromPrincipal(principal, "camera");
+ SitePermissions.removeFromPrincipal(principal, "desktop-notification");
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []);
+
+ Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0);
+ SitePermissions.setForPrincipal(
+ principal,
+ "shortcuts",
+ SitePermissions.BLOCK
+ );
+
+ // Customized preference should have been enabled, but the default should not.
+ Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0);
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [
+ {
+ id: "shortcuts",
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ ]);
+
+ SitePermissions.removeFromPrincipal(principal, "shortcuts");
+ Services.prefs.clearUserPref("permissions.default.shortcuts");
+});
+
+add_task(async function testGetAvailableStates() {
+ Assert.deepEqual(SitePermissions.getAvailableStates("camera"), [
+ SitePermissions.UNKNOWN,
+ SitePermissions.ALLOW,
+ SitePermissions.BLOCK,
+ ]);
+
+ // Test available states with a default permission set.
+ Services.prefs.setIntPref(
+ "permissions.default.camera",
+ SitePermissions.ALLOW
+ );
+ Assert.deepEqual(SitePermissions.getAvailableStates("camera"), [
+ SitePermissions.PROMPT,
+ SitePermissions.ALLOW,
+ SitePermissions.BLOCK,
+ ]);
+ Services.prefs.clearUserPref("permissions.default.camera");
+
+ Assert.deepEqual(SitePermissions.getAvailableStates("cookie"), [
+ SitePermissions.ALLOW,
+ SitePermissions.ALLOW_COOKIES_FOR_SESSION,
+ SitePermissions.BLOCK,
+ ]);
+
+ Assert.deepEqual(SitePermissions.getAvailableStates("popup"), [
+ SitePermissions.ALLOW,
+ SitePermissions.BLOCK,
+ ]);
+});
+
+add_task(async function testExactHostMatch() {
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://example.com"
+ );
+ let subPrincipal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://test1.example.com"
+ );
+
+ let exactHostMatched = [
+ "autoplay-media",
+ "desktop-notification",
+ "focus-tab-by-prompt",
+ "camera",
+ "microphone",
+ "screen",
+ "geo",
+ "xr",
+ "persistent-storage",
+ ];
+ if (RESIST_FINGERPRINTING_ENABLED) {
+ // Canvas permission should be hidden unless privacy.resistFingerprinting
+ // is true.
+ exactHostMatched.push("canvas");
+ }
+ if (MIDI_ENABLED) {
+ // WebMIDI is only pref'd on in nightly.
+ // Should remove this checking and add it as default after it is fully pref-on.
+ exactHostMatched.push("midi");
+ exactHostMatched.push("midi-sysex");
+ }
+ if (EXT_PROTOCOL_ENABLED) {
+ exactHostMatched.push("open-protocol-handler");
+ }
+ let nonExactHostMatched = [
+ "cookie",
+ "popup",
+ "install",
+ "shortcuts",
+ "storage-access",
+ ];
+ if (STATE_PARTITIONING_ENABLED) {
+ nonExactHostMatched.push("3rdPartyStorage");
+ }
+
+ let permissions = SitePermissions.listPermissions();
+ for (let permission of permissions) {
+ SitePermissions.setForPrincipal(
+ principal,
+ permission,
+ SitePermissions.ALLOW
+ );
+
+ if (exactHostMatched.includes(permission)) {
+ // Check that the sub-origin does not inherit the permission from its parent.
+ Assert.equal(
+ SitePermissions.getForPrincipal(subPrincipal, permission).state,
+ SitePermissions.getDefault(permission),
+ `${permission} should exact-host match`
+ );
+ } else if (nonExactHostMatched.includes(permission)) {
+ // Check that the sub-origin does inherit the permission from its parent.
+ Assert.equal(
+ SitePermissions.getForPrincipal(subPrincipal, permission).state,
+ SitePermissions.ALLOW,
+ `${permission} should not exact-host match`
+ );
+ } else {
+ Assert.ok(
+ false,
+ `Found an unknown permission ${permission} in exact host match test.` +
+ "Please add new permissions from SitePermissions.jsm to this test."
+ );
+ }
+
+ // Check that the permission can be made specific to the sub-origin.
+ SitePermissions.setForPrincipal(
+ subPrincipal,
+ permission,
+ SitePermissions.PROMPT
+ );
+ Assert.equal(
+ SitePermissions.getForPrincipal(subPrincipal, permission).state,
+ SitePermissions.PROMPT
+ );
+ Assert.equal(
+ SitePermissions.getForPrincipal(principal, permission).state,
+ SitePermissions.ALLOW
+ );
+
+ SitePermissions.removeFromPrincipal(subPrincipal, permission);
+ SitePermissions.removeFromPrincipal(principal, permission);
+ }
+});
+
+add_task(async function testDefaultPrefs() {
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://example.com"
+ );
+
+ // Check that without a pref the default return value is UNKNOWN.
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // Check that the default return value changed after setting the pref.
+ Services.prefs.setIntPref(
+ "permissions.default.camera",
+ SitePermissions.BLOCK
+ );
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // Check that other permissions still return UNKNOWN.
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, "microphone"), {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // Check that the default return value changed after changing the pref.
+ Services.prefs.setIntPref(
+ "permissions.default.camera",
+ SitePermissions.ALLOW
+ );
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), {
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // Check that the preference is ignored if there is a value.
+ SitePermissions.setForPrincipal(principal, "camera", SitePermissions.BLOCK);
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), {
+ state: SitePermissions.BLOCK,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // The preference should be honored again, after resetting the permissions.
+ SitePermissions.removeFromPrincipal(principal, "camera");
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), {
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+
+ // Should be UNKNOWN after clearing the pref.
+ Services.prefs.clearUserPref("permissions.default.camera");
+ Assert.deepEqual(SitePermissions.getForPrincipal(principal, "camera"), {
+ state: SitePermissions.UNKNOWN,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ });
+});
+
+add_task(async function testCanvasPermission() {
+ let resistFingerprinting = Services.prefs.getBoolPref(
+ "privacy.resistFingerprinting",
+ false
+ );
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://example.com"
+ );
+
+ SitePermissions.setForPrincipal(principal, "canvas", SitePermissions.ALLOW);
+
+ // Canvas permission is hidden when privacy.resistFingerprinting is false.
+ Services.prefs.setBoolPref("privacy.resistFingerprinting", false);
+ Assert.equal(SitePermissions.listPermissions().indexOf("canvas"), -1);
+ Assert.equal(
+ SitePermissions.getAllByPrincipal(principal).filter(
+ permission => permission.id === "canvas"
+ ).length,
+ 0
+ );
+
+ // Canvas permission is visible when privacy.resistFingerprinting is true.
+ Services.prefs.setBoolPref("privacy.resistFingerprinting", true);
+ Assert.notEqual(SitePermissions.listPermissions().indexOf("canvas"), -1);
+ Assert.notEqual(
+ SitePermissions.getAllByPrincipal(principal).filter(
+ permission => permission.id === "canvas"
+ ).length,
+ 0
+ );
+
+ SitePermissions.removeFromPrincipal(principal, "canvas");
+ Services.prefs.setBoolPref(
+ "privacy.resistFingerprinting",
+ resistFingerprinting
+ );
+});
+
+add_task(async function testFilePermissions() {
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "file:///example.js"
+ );
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []);
+
+ SitePermissions.setForPrincipal(principal, "camera", SitePermissions.ALLOW);
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), [
+ {
+ id: "camera",
+ state: SitePermissions.ALLOW,
+ scope: SitePermissions.SCOPE_PERSISTENT,
+ },
+ ]);
+ SitePermissions.removeFromPrincipal(principal, "camera");
+ Assert.deepEqual(SitePermissions.getAllByPrincipal(principal), []);
+});
diff --git a/browser/modules/test/unit/test_discovery.js b/browser/modules/test/unit/test_discovery.js
new file mode 100644
index 0000000000..2d3205abfb
--- /dev/null
+++ b/browser/modules/test/unit/test_discovery.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* globals ChromeUtils, Assert, add_task */
+"use strict";
+
+// ClientID fails without...
+do_get_profile();
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { TestUtils } = ChromeUtils.import(
+ "resource://testing-common/TestUtils.jsm"
+);
+const { ClientID } = ChromeUtils.import("resource://gre/modules/ClientID.jsm");
+const { Discovery } = ChromeUtils.import("resource:///modules/Discovery.jsm");
+const { ContextualIdentityService } = ChromeUtils.import(
+ "resource://gre/modules/ContextualIdentityService.jsm"
+);
+
+const TAAR_COOKIE_NAME = "taarId";
+
+add_task(async function test_discovery() {
+ let uri = Services.io.newURI("https://example.com/foobar");
+
+ // Ensure the prefs we need
+ Services.prefs.setBoolPref("browser.discovery.enabled", true);
+ Services.prefs.setBoolPref("browser.discovery.containers.enabled", true);
+ Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true);
+ Services.prefs.setCharPref("browser.discovery.sites", uri.host);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.discovery.enabled");
+ Services.prefs.clearUserPref("browser.discovery.containers.enabled");
+ Services.prefs.clearUserPref("browser.discovery.sites");
+ Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
+ });
+
+ // This is normally initialized by telemetry, force id creation. This results
+ // in Discovery setting the cookie.
+ await ClientID.getClientID();
+ await Discovery.update();
+
+ ok(
+ Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {}),
+ "cookie exists"
+ );
+ ok(
+ !Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {
+ privateBrowsingId: 1,
+ }),
+ "no private cookie exists"
+ );
+ ContextualIdentityService.getPublicIdentities().forEach(identity => {
+ let { userContextId } = identity;
+ equal(
+ Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {
+ userContextId,
+ }),
+ identity.public,
+ "cookie exists"
+ );
+ });
+
+ // Test the addition of a new container.
+ let changed = TestUtils.topicObserved("cookie-changed", (subject, data) => {
+ let cookie = subject.QueryInterface(Ci.nsICookie);
+ equal(cookie.name, TAAR_COOKIE_NAME, "taar cookie exists");
+ equal(cookie.host, uri.host, "cookie exists for host");
+ equal(
+ cookie.originAttributes.userContextId,
+ container.userContextId,
+ "cookie userContextId is correct"
+ );
+ return true;
+ });
+ let container = ContextualIdentityService.create(
+ "New Container",
+ "Icon",
+ "Color"
+ );
+ await changed;
+
+ // Test disabling
+ Discovery.enabled = false;
+ // Wait for the update to remove the cookie.
+ await TestUtils.waitForCondition(() => {
+ return !Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {});
+ });
+
+ ContextualIdentityService.getPublicIdentities().forEach(identity => {
+ let { userContextId } = identity;
+ ok(
+ !Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {
+ userContextId,
+ }),
+ "no cookie exists"
+ );
+ });
+
+ // turn off containers
+ Services.prefs.setBoolPref("browser.discovery.containers.enabled", false);
+
+ Discovery.enabled = true;
+ await TestUtils.waitForCondition(() => {
+ return Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {});
+ });
+ // make sure we did not set cookies on containers
+ ContextualIdentityService.getPublicIdentities().forEach(identity => {
+ let { userContextId } = identity;
+ ok(
+ !Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {
+ userContextId,
+ }),
+ "no cookie exists"
+ );
+ });
+
+ // Make sure clientId changes update discovery
+ changed = TestUtils.topicObserved("cookie-changed", (subject, data) => {
+ if (data !== "added") {
+ return false;
+ }
+ let cookie = subject.QueryInterface(Ci.nsICookie);
+ equal(cookie.name, TAAR_COOKIE_NAME, "taar cookie exists");
+ equal(cookie.host, uri.host, "cookie exists for host");
+ return true;
+ });
+ await ClientID.removeClientIDs();
+ await ClientID.getClientID();
+ await changed;
+
+ // Make sure disabling telemetry disables discovery.
+ Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", false);
+ await TestUtils.waitForCondition(() => {
+ return !Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {});
+ });
+});
diff --git a/browser/modules/test/unit/xpcshell.ini b/browser/modules/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..1749bb2744
--- /dev/null
+++ b/browser/modules/test/unit/xpcshell.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+head =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_E10SUtils_nested_URIs.js]
+[test_HomePage.js]
+[test_HomePage_ignore.js]
+[test_Sanitizer_interrupted.js]
+[test_SitePermissions.js]
+[test_SiteDataManager.js]
+[test_LaterRun.js]
+[test_discovery.js]
+[test_PingCentre.js]
+[test_ProfileCounter.js]
+skip-if = os != 'win' # Test of a Windows-specific feature