summaryrefslogtreecommitdiffstats
path: root/browser/components/attribution/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/attribution/test/browser')
-rw-r--r--browser/components/attribution/test/browser/browser.ini9
-rw-r--r--browser/components/attribution/test/browser/browser_AttributionCode_Mac_telemetry.js248
-rw-r--r--browser/components/attribution/test/browser/browser_AttributionCode_telemetry.js101
-rw-r--r--browser/components/attribution/test/browser/head.js23
4 files changed, 381 insertions, 0 deletions
diff --git a/browser/components/attribution/test/browser/browser.ini b/browser/components/attribution/test/browser/browser.ini
new file mode 100644
index 0000000000..080df0afef
--- /dev/null
+++ b/browser/components/attribution/test/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_AttributionCode_Mac_telemetry.js]
+skip-if = toolkit != "cocoa" # macOS only telemetry.
+[browser_AttributionCode_telemetry.js]
+skip-if = (os != "win" && toolkit != "cocoa") # Windows and macOS only telemetry.
+
diff --git a/browser/components/attribution/test/browser/browser_AttributionCode_Mac_telemetry.js b/browser/components/attribution/test/browser/browser_AttributionCode_Mac_telemetry.js
new file mode 100644
index 0000000000..7523f0f930
--- /dev/null
+++ b/browser/components/attribution/test/browser/browser_AttributionCode_Mac_telemetry.js
@@ -0,0 +1,248 @@
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+);
+const { AttributionIOUtils } = ChromeUtils.importESModule(
+ "resource:///modules/AttributionCode.sys.mjs"
+);
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+async function assertCacheExistsAndIsEmpty() {
+ // We should have written to the cache, and be able to read back
+ // with no errors.
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+ histogram.clear();
+
+ ok(await AttributionIOUtils.exists(AttributionCode.attributionFile.path));
+ Assert.deepEqual(
+ "",
+ new TextDecoder().decode(
+ await AttributionIOUtils.read(AttributionCode.attributionFile.path)
+ )
+ );
+
+ AttributionCode._clearCache();
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {}, "Should be able to get cached result");
+
+ Assert.deepEqual({}, histogram.snapshot().values || {});
+}
+
+add_task(async function test_write_error() {
+ const sandbox = sinon.createSandbox();
+ let attributionSvc = Cc["@mozilla.org/mac-attribution;1"].getService(
+ Ci.nsIMacAttributionService
+ );
+ attributionSvc.setReferrerUrl(
+ MacAttribution.applicationPath,
+ "https://example.com?content=content",
+ true
+ );
+
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+
+ let oldExists = AttributionIOUtils.exists;
+ let oldWrite = AttributionIOUtils.write;
+ try {
+ // Clear any existing telemetry
+ histogram.clear();
+
+ // Force the file to not exist and then cause a write error. This is delicate
+ // because various background tasks may invoke `IOUtils.writeAtomic` while
+ // this test is running. Be careful to only stub the one call.
+ AttributionIOUtils.exists = () => false;
+ AttributionIOUtils.write = () => {
+ throw new Error("write_error");
+ };
+
+ // Try to read the attribution code.
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ { content: "content" },
+ "Should be able to get a result even if the file doesn't write"
+ );
+
+ TelemetryTestUtils.assertHistogram(histogram, INDEX_WRITE_ERROR, 1);
+ } finally {
+ AttributionIOUtils.exists = oldExists;
+ AttributionIOUtils.write = oldWrite;
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ histogram.clear();
+ sandbox.restore();
+ }
+});
+
+add_task(async function test_unusual_referrer() {
+ // This referrer URL looks malformed, but the malformed bits are dropped, so
+ // it's actually ok. This is what allows extraneous bits like `fbclid` tags
+ // to be ignored.
+ let attributionSvc = Cc["@mozilla.org/mac-attribution;1"].getService(
+ Ci.nsIMacAttributionService
+ );
+ attributionSvc.setReferrerUrl(
+ MacAttribution.applicationPath,
+ "https://example.com?content=&=campaign",
+ true
+ );
+
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+ try {
+ // Clear any existing telemetry
+ histogram.clear();
+
+ // Try to read the attribution code
+ await AttributionCode.getAttrDataAsync();
+
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {}, "Should be able to get empty result");
+
+ Assert.deepEqual({}, histogram.snapshot().values || {});
+
+ await assertCacheExistsAndIsEmpty();
+ } finally {
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ histogram.clear();
+ }
+});
+
+add_task(async function test_blank_referrer() {
+ let attributionSvc = Cc["@mozilla.org/mac-attribution;1"].getService(
+ Ci.nsIMacAttributionService
+ );
+ attributionSvc.setReferrerUrl(MacAttribution.applicationPath, "", true);
+
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+
+ try {
+ // Clear any existing telemetry
+ histogram.clear();
+
+ // Try to read the attribution code
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {}, "Should be able to get empty result");
+
+ Assert.deepEqual({}, histogram.snapshot().values || {});
+
+ await assertCacheExistsAndIsEmpty();
+ } finally {
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ histogram.clear();
+ }
+});
+
+add_task(async function test_no_referrer() {
+ const sandbox = sinon.createSandbox();
+ let newApplicationPath = MacAttribution.applicationPath + ".test";
+ sandbox.stub(MacAttribution, "applicationPath").get(() => newApplicationPath);
+
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+ try {
+ // Clear any existing telemetry
+ histogram.clear();
+
+ // Try to read the attribution code
+ await AttributionCode.getAttrDataAsync();
+
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {}, "Should be able to get empty result");
+
+ Assert.deepEqual({}, histogram.snapshot().values || {});
+
+ await assertCacheExistsAndIsEmpty();
+ } finally {
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ histogram.clear();
+ sandbox.restore();
+ }
+});
+
+add_task(async function test_broken_referrer() {
+ let attributionSvc = Cc["@mozilla.org/mac-attribution;1"].getService(
+ Ci.nsIMacAttributionService
+ );
+ attributionSvc.setReferrerUrl(
+ MacAttribution.applicationPath,
+ "https://example.com?content=content",
+ true
+ );
+
+ // This uses macOS internals to change the GUID so that it will look like the
+ // application has quarantine data but nothing will be pressent in the
+ // quarantine database. This shouldn't happen in the wild.
+ function generateQuarantineGUID() {
+ let str = Services.uuid.generateUUID().toString().toUpperCase();
+ // Strip {}.
+ return str.substring(1, str.length - 1);
+ }
+
+ // These magic constants are macOS GateKeeper flags.
+ let string = [
+ "01c1",
+ "5991b778",
+ "Safari.app",
+ generateQuarantineGUID(),
+ ].join(";");
+ let bytes = new TextEncoder().encode(string);
+ await IOUtils.setMacXAttr(
+ MacAttribution.applicationPath,
+ "com.apple.quarantine",
+ bytes
+ );
+
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+ try {
+ // Clear any existing telemetry
+ histogram.clear();
+
+ // Try to read the attribution code
+ await AttributionCode.getAttrDataAsync();
+
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {}, "Should be able to get empty result");
+
+ TelemetryTestUtils.assertHistogram(histogram, INDEX_QUARANTINE_ERROR, 1);
+ histogram.clear();
+
+ await assertCacheExistsAndIsEmpty();
+ } finally {
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ histogram.clear();
+ }
+});
diff --git a/browser/components/attribution/test/browser/browser_AttributionCode_telemetry.js b/browser/components/attribution/test/browser/browser_AttributionCode_telemetry.js
new file mode 100644
index 0000000000..43888a1e74
--- /dev/null
+++ b/browser/components/attribution/test/browser/browser_AttributionCode_telemetry.js
@@ -0,0 +1,101 @@
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+const { AttributionIOUtils } = ChromeUtils.importESModule(
+ "resource:///modules/AttributionCode.sys.mjs"
+);
+
+add_task(async function test_parse_error() {
+ if (AppConstants.platform == "macosx") {
+ // On macOS, the underlying data is the OS-level quarantine
+ // database. We need to start from nothing to isolate the cache.
+ const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+ );
+ let attributionSvc = Cc["@mozilla.org/mac-attribution;1"].getService(
+ Ci.nsIMacAttributionService
+ );
+ attributionSvc.setReferrerUrl(MacAttribution.applicationPath, "", true);
+ }
+
+ registerCleanupFunction(async () => {
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ });
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+ // Delete the file to trigger a read error
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ // Clear any existing telemetry
+ histogram.clear();
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ {},
+ "Shouldn't be able to get a result if the file doesn't exist"
+ );
+
+ // Write an invalid file to trigger a decode error
+ // Skip this for MSIX packages though - we can't write or delete
+ // the attribution file there, everything happens in memory instead.
+ if (
+ AppConstants.platform === "win" &&
+ !Services.sysinfo.getProperty("hasWinPackageId")
+ ) {
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ // Empty string is valid on macOS.
+ await AttributionCode.writeAttributionFile(
+ AppConstants.platform == "macosx" ? "invalid" : ""
+ );
+ result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {}, "Should have failed to parse");
+
+ // `assertHistogram` also ensures that `read_error` index 0 is 0
+ // as we should not have recorded telemetry from the previous `getAttrDataAsync` call
+ TelemetryTestUtils.assertHistogram(histogram, INDEX_DECODE_ERROR, 1);
+ // Reset
+ histogram.clear();
+ }
+});
+
+add_task(async function test_read_error() {
+ registerCleanupFunction(async () => {
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ });
+ const histogram = Services.telemetry.getHistogramById(
+ "BROWSER_ATTRIBUTION_ERRORS"
+ );
+
+ // Delete the file to trigger a read error
+ await AttributionCode.deleteFileAsync();
+ AttributionCode._clearCache();
+ // Clear any existing telemetry
+ histogram.clear();
+
+ // Force the file to exist but then cause a read error
+ let oldExists = AttributionIOUtils.exists;
+ AttributionIOUtils.exists = () => true;
+
+ let oldRead = AttributionIOUtils.read;
+ AttributionIOUtils.read = () => {
+ throw new Error("read_error");
+ };
+
+ registerCleanupFunction(() => {
+ AttributionIOUtils.exists = oldExists;
+ AttributionIOUtils.read = oldRead;
+ });
+
+ // Try to read the file
+ await AttributionCode.getAttrDataAsync();
+
+ // It should record the read error
+ TelemetryTestUtils.assertHistogram(histogram, INDEX_READ_ERROR, 1);
+
+ // Clear any existing telemetry
+ histogram.clear();
+});
diff --git a/browser/components/attribution/test/browser/head.js b/browser/components/attribution/test/browser/head.js
new file mode 100644
index 0000000000..61b24284ee
--- /dev/null
+++ b/browser/components/attribution/test/browser/head.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const { AttributionCode } = ChromeUtils.importESModule(
+ "resource:///modules/AttributionCode.sys.mjs"
+);
+
+// Keep in sync with `BROWSER_ATTRIBUTION_ERRORS` in Histograms.json.
+const INDEX_READ_ERROR = 0;
+const INDEX_DECODE_ERROR = 1;
+const INDEX_WRITE_ERROR = 2;
+const INDEX_QUARANTINE_ERROR = 3;
+
+add_setup(function () {
+ // AttributionCode._clearCache is only possible in a testing environment
+ Services.env.set("XPCSHELL_TEST_PROFILE_DIR", "testing");
+
+ registerCleanupFunction(() => {
+ Services.env.set("XPCSHELL_TEST_PROFILE_DIR", null);
+ });
+});