diff options
Diffstat (limited to 'browser/modules/test/unit')
-rw-r--r-- | browser/modules/test/unit/test_E10SUtils_nested_URIs.js | 93 | ||||
-rw-r--r-- | browser/modules/test/unit/test_HomePage.js | 91 | ||||
-rw-r--r-- | browser/modules/test/unit/test_HomePage_ignore.js | 133 | ||||
-rw-r--r-- | browser/modules/test/unit/test_LaterRun.js | 243 | ||||
-rw-r--r-- | browser/modules/test/unit/test_PingCentre.js | 144 | ||||
-rw-r--r-- | browser/modules/test/unit/test_ProfileCounter.js | 244 | ||||
-rw-r--r-- | browser/modules/test/unit/test_Sanitizer_interrupted.js | 139 | ||||
-rw-r--r-- | browser/modules/test/unit/test_SiteDataManager.js | 227 | ||||
-rw-r--r-- | browser/modules/test/unit/test_SitePermissions.js | 393 | ||||
-rw-r--r-- | browser/modules/test/unit/test_discovery.js | 138 | ||||
-rw-r--r-- | browser/modules/test/unit/xpcshell.ini | 16 |
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 |