Skip to content

Commit 352258d

Browse files
committed
Bug 1865845: stop caching mac attribution data in a separate file r=nalexander
This is mostly removing code and tests related to reading/writing the cache file on macOS and updating of tests. Aside from that, the most notable part is the change to `setAttributionString` to automatically prepend `__MOZCUSTOM__` when writing an attribution code. This is mostly done to make things simpler and cleaner in the majority of the tests, but seeing as `getAttributionString` is also aware of it, it seems like a generally nicer way to do this. Differential Revision: https://phabricator.services.mozilla.com/D197204
1 parent 8d7a597 commit 352258d

12 files changed

+88
-199
lines changed

browser/components/attribution/AttributionCode.sys.mjs

Lines changed: 22 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
1616
const lazy = {};
1717
ChromeUtils.defineESModuleGetters(lazy, {
1818
MacAttribution: "resource:///modules/MacAttribution.sys.mjs",
19-
UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
2019
});
2120
ChromeUtils.defineLazyGetter(lazy, "log", () => {
2221
let { ConsoleAPI } = ChromeUtils.importESModule(
@@ -82,49 +81,6 @@ export var AttributionCode = {
8281
let file = Services.dirsvc.get("GreD", Ci.nsIFile);
8382
file.append("postSigningData");
8483
return file;
85-
} else if (AppConstants.platform == "macosx") {
86-
// There's no `UpdRootD` in xpcshell tests. Some existing tests override
87-
// it, which is onerous and difficult to share across tests. When testing,
88-
// if it's not defined, fallback to a nested subdirectory of the xpcshell
89-
// temp directory. Nesting more closely replicates the situation where the
90-
// update directory does not (yet) exist, testing a scenario witnessed in
91-
// development.
92-
let file;
93-
try {
94-
file = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
95-
} catch (ex) {
96-
// It's most common to test for the profile dir, even though we actually
97-
// are using the temp dir.
98-
if (
99-
ex instanceof Ci.nsIException &&
100-
ex.result == Cr.NS_ERROR_FAILURE &&
101-
Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")
102-
) {
103-
let path = Services.env.get("XPCSHELL_TEST_TEMP_DIR");
104-
file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
105-
file.initWithPath(path);
106-
file.append("nested_UpdRootD_1");
107-
file.append("nested_UpdRootD_2");
108-
} else {
109-
throw ex;
110-
}
111-
}
112-
// Note: this file is in a location that includes the absolute path
113-
// to the running install, and the filename includes the update channel.
114-
// To ensure consistency regardless of when `attributionFile` is accessed we
115-
// explicitly do not include partner IDs that may be part of the full update channel.
116-
// These are not necessarily applied when this is first accessed, and we want to
117-
// ensure consistency between early and late accesses.
118-
// Partner builds never contain attribution information, so this has no known
119-
// consequences.
120-
// For example:
121-
// ~/Library/Caches/Mozilla/updates/Applications/Firefox/macAttributionDataCache-release
122-
// This is done to ensure that attribution data is preserved through a
123-
// pave over install of an install on the same channel.
124-
file.append(
125-
"macAttributionDataCache-" + lazy.UpdateUtils.getUpdateChannel(false)
126-
);
127-
return file;
12884
}
12985

13086
return null;
@@ -135,8 +91,8 @@ export var AttributionCode = {
13591
* @param {String} code to write.
13692
*/
13793
async writeAttributionFile(code) {
138-
// Writing attribution files is only used as part of test code, and Mac
139-
// attribution, so bailing here for MSIX builds is no big deal.
94+
// Writing attribution files is only used as part of test code
95+
// so bailing here for MSIX builds is no big deal.
14096
if (
14197
AppConstants.platform === "win" &&
14298
Services.sysinfo.getProperty("hasWinPackageId")
@@ -228,14 +184,12 @@ export var AttributionCode = {
228184
},
229185

230186
async _getMacAttrDataAsync() {
231-
let attributionFile = this.attributionFile;
232-
233187
// On macOS, we fish the attribution data from an extended attribute on
234188
// the .app bundle directory.
235189
try {
236190
let attrStr = await lazy.MacAttribution.getAttributionString();
237191
lazy.log.debug(
238-
`getAttrDataAsync: macOS attribution getAttributionString: "${attrStr}"`
192+
`_getMacAttrDataAsync: getAttributionString: "${attrStr}"`
239193
);
240194

241195
gCachedAttrData = this.parseAttributionCode(attrStr);
@@ -261,22 +215,6 @@ export var AttributionCode = {
261215
`macOS attribution data is ${JSON.stringify(gCachedAttrData)}`
262216
);
263217

264-
// We only want to try to fetch the attribution string once on macOS
265-
try {
266-
let code = this.serializeAttributionData(gCachedAttrData);
267-
lazy.log.debug(`macOS attribution data serializes as "${code}"`);
268-
await this.writeAttributionFile(code);
269-
} catch (ex) {
270-
lazy.log.debug(`Caught exception writing "${attributionFile.path}"`, ex);
271-
Services.telemetry
272-
.getHistogramById("BROWSER_ATTRIBUTION_ERRORS")
273-
.add("write_error");
274-
return gCachedAttrData;
275-
}
276-
277-
lazy.log.debug(
278-
`Returning after successfully writing "${attributionFile.path}"`
279-
);
280218
return gCachedAttrData;
281219
},
282220

@@ -308,6 +246,10 @@ export var AttributionCode = {
308246
* strip "utm_" while retrieving the params.
309247
*/
310248
async getAttrDataAsync() {
249+
if (AppConstants.platform != "win" && AppConstants.platform != "macosx") {
250+
// This platform doesn't support attribution.
251+
return gCachedAttrData;
252+
}
311253
if (gCachedAttrData != null) {
312254
lazy.log.debug(
313255
`getAttrDataAsync: attribution is cached: ${JSON.stringify(
@@ -329,37 +271,25 @@ export var AttributionCode = {
329271
}
330272

331273
gCachedAttrData = {};
332-
let attributionFile = this.attributionFile;
333-
if (!attributionFile) {
334-
// This platform doesn't support attribution.
335-
lazy.log.debug(
336-
`getAttrDataAsync: no attribution (attributionFile is null)`
337-
);
338-
return gCachedAttrData;
339-
}
340274

341-
if (
342-
AppConstants.platform == "macosx" &&
343-
!(await AttributionIOUtils.exists(attributionFile.path))
344-
) {
345-
lazy.log.debug(
346-
`getAttrDataAsync: macOS && !exists("${attributionFile.path}")`
347-
);
275+
if (AppConstants.platform == "macosx") {
276+
lazy.log.debug(`getAttrDataAsync: macOS`);
348277
return this._getMacAttrDataAsync();
349278
}
350279

351-
lazy.log.debug(
352-
`getAttrDataAsync: !macOS || !exists("${attributionFile.path}")`
353-
);
280+
lazy.log.debug("getAttrDataAsync: !macOS");
354281

282+
let attributionFile = this.attributionFile;
355283
let bytes;
356284
try {
357285
if (
358286
AppConstants.platform === "win" &&
359287
Services.sysinfo.getProperty("hasWinPackageId")
360288
) {
289+
lazy.log.debug("getAttrDataAsync: MSIX");
361290
bytes = await this._getWindowsMSIXAttrDataAsync();
362291
} else {
292+
lazy.log.debug("getAttrDataAsync: NSIS");
363293
bytes = await this._getWindowsNSISAttrDataAsync();
364294
}
365295
} catch (ex) {
@@ -429,12 +359,15 @@ export var AttributionCode = {
429359
* or if the file couldn't be deleted (the promise is never rejected).
430360
*/
431361
async deleteFileAsync() {
432-
try {
433-
await IOUtils.remove(this.attributionFile.path);
434-
} catch (ex) {
435-
// The attribution file may already have been deleted,
436-
// or it may have never been installed at all;
437-
// failure to delete it isn't an error.
362+
// There is no cache file on macOS
363+
if (AppConstants.platform == "win") {
364+
try {
365+
await IOUtils.remove(this.attributionFile.path);
366+
} catch (ex) {
367+
// The attribution file may already have been deleted,
368+
// or it may have never been installed at all;
369+
// failure to delete it isn't an error.
370+
}
438371
}
439372
},
440373

browser/components/attribution/MacAttribution.sys.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export var MacAttribution = {
1818
return IOUtils.setMacXAttr(
1919
path,
2020
"com.apple.application-instance",
21-
new TextEncoder().encode(aAttrStr)
21+
new TextEncoder().encode(`__MOZCUSTOM__${aAttrStr}`)
2222
);
2323
},
2424

browser/components/attribution/test/browser/browser.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ prefs = ["browser.attribution.macos.enabled=true"]
66
skip-if = ["toolkit != 'cocoa'"] # macOS only telemetry.
77

88
["browser_AttributionCode_telemetry.js"]
9-
skip-if = ["(os != 'win' && toolkit != 'cocoa')"] # Windows and macOS only telemetry.
9+
# These tests only cover the attribution cache file - which only exists on
10+
# Windows.
11+
skip-if = ["os != 'win'"]

browser/components/attribution/test/browser/browser_AttributionCode_Mac_telemetry.js

Lines changed: 12 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -11,77 +11,20 @@ const { sinon } = ChromeUtils.importESModule(
1111
"resource://testing-common/Sinon.sys.mjs"
1212
);
1313

14-
async function assertCacheExistsAndIsEmpty() {
15-
// We should have written to the cache, and be able to read back
16-
// with no errors.
17-
const histogram = Services.telemetry.getHistogramById(
18-
"BROWSER_ATTRIBUTION_ERRORS"
19-
);
20-
histogram.clear();
21-
22-
ok(await AttributionIOUtils.exists(AttributionCode.attributionFile.path));
23-
Assert.deepEqual(
24-
"",
25-
new TextDecoder().decode(
26-
await AttributionIOUtils.read(AttributionCode.attributionFile.path)
27-
)
28-
);
29-
30-
AttributionCode._clearCache();
31-
let result = await AttributionCode.getAttrDataAsync();
32-
Assert.deepEqual(result, {}, "Should be able to get cached result");
33-
34-
Assert.deepEqual({}, histogram.snapshot().values || {});
35-
}
36-
37-
add_task(async function test_write_error() {
38-
const sandbox = sinon.createSandbox();
39-
await MacAttribution.setAttributionString("__MOZCUSTOM__content%3Dcontent");
40-
41-
await AttributionCode.deleteFileAsync();
42-
AttributionCode._clearCache();
43-
44-
const histogram = Services.telemetry.getHistogramById(
45-
"BROWSER_ATTRIBUTION_ERRORS"
46-
);
47-
48-
let oldExists = AttributionIOUtils.exists;
49-
let oldWrite = AttributionIOUtils.write;
50-
try {
51-
// Clear any existing telemetry
52-
histogram.clear();
53-
54-
// Force the file to not exist and then cause a write error. This is delicate
55-
// because various background tasks may invoke `IOUtils.writeAtomic` while
56-
// this test is running. Be careful to only stub the one call.
57-
AttributionIOUtils.exists = () => false;
58-
AttributionIOUtils.write = () => {
59-
throw new Error("write_error");
60-
};
61-
62-
// Try to read the attribution code.
63-
let result = await AttributionCode.getAttrDataAsync();
64-
Assert.deepEqual(
65-
result,
66-
{ content: "content" },
67-
"Should be able to get a result even if the file doesn't write"
68-
);
69-
70-
TelemetryTestUtils.assertHistogram(histogram, INDEX_WRITE_ERROR, 1);
71-
} finally {
72-
AttributionIOUtils.exists = oldExists;
73-
AttributionIOUtils.write = oldWrite;
74-
await AttributionCode.deleteFileAsync();
75-
AttributionCode._clearCache();
76-
histogram.clear();
77-
sandbox.restore();
78-
}
79-
});
80-
8114
add_task(async function test_blank_attribution() {
8215
// Ensure no attribution information is present
83-
await MacAttribution.delAttributionString();
84-
await AttributionCode.deleteFileAsync();
16+
try {
17+
await MacAttribution.delAttributionString();
18+
} catch (ex) {
19+
// NS_ERROR_DOM_NOT_FOUND_ERR means there was not an attribution
20+
// string to delete - which we can safely ignore.
21+
if (
22+
!(ex instanceof Ci.nsIException) ||
23+
ex.result != Cr.NS_ERROR_DOM_NOT_FOUND_ERR
24+
) {
25+
throw ex;
26+
}
27+
}
8528
AttributionCode._clearCache();
8629

8730
const histogram = Services.telemetry.getHistogramById(
@@ -97,10 +40,7 @@ add_task(async function test_blank_attribution() {
9740
Assert.deepEqual(result, {}, "Should be able to get empty result");
9841

9942
Assert.deepEqual({}, histogram.snapshot().values || {});
100-
101-
await assertCacheExistsAndIsEmpty();
10243
} finally {
103-
await AttributionCode.deleteFileAsync();
10444
AttributionCode._clearCache();
10545
histogram.clear();
10646
}
@@ -111,7 +51,6 @@ add_task(async function test_no_attribution() {
11151
let newApplicationPath = MacAttribution.applicationPath + ".test";
11252
sandbox.stub(MacAttribution, "applicationPath").get(() => newApplicationPath);
11353

114-
await AttributionCode.deleteFileAsync();
11554
AttributionCode._clearCache();
11655

11756
const histogram = Services.telemetry.getHistogramById(
@@ -128,10 +67,7 @@ add_task(async function test_no_attribution() {
12867
Assert.deepEqual(result, {}, "Should be able to get empty result");
12968

13069
Assert.deepEqual({}, histogram.snapshot().values || {});
131-
132-
await assertCacheExistsAndIsEmpty();
13370
} finally {
134-
await AttributionCode.deleteFileAsync();
13571
AttributionCode._clearCache();
13672
histogram.clear();
13773
sandbox.restore();

browser/components/attribution/test/browser/browser_AttributionCode_telemetry.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@ const { AttributionIOUtils } = ChromeUtils.importESModule(
66
);
77

88
add_task(async function test_parse_error() {
9-
if (AppConstants.platform == "macosx") {
10-
const { MacAttribution } = ChromeUtils.importESModule(
11-
"resource:///modules/MacAttribution.sys.mjs"
12-
);
13-
MacAttribution.setAttributionString("");
14-
}
15-
169
registerCleanupFunction(async () => {
1710
await AttributionCode.deleteFileAsync();
1811
AttributionCode._clearCache();
@@ -41,10 +34,7 @@ add_task(async function test_parse_error() {
4134
) {
4235
await AttributionCode.deleteFileAsync();
4336
AttributionCode._clearCache();
44-
// Empty string is valid on macOS.
45-
await AttributionCode.writeAttributionFile(
46-
AppConstants.platform == "macosx" ? "invalid" : ""
47-
);
37+
await AttributionCode.writeAttributionFile("");
4838
result = await AttributionCode.getAttrDataAsync();
4939
Assert.deepEqual(result, {}, "Should have failed to parse");
5040

browser/components/attribution/test/xpcshell/test_AttributionCode.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ add_task(async function testValidAttrCodes() {
3939
// are not URI encoded, and the AttributionCode code that deals with
4040
// them expects that - so we have to simulate that as well.
4141
msixCampaignIdStub.callsFake(async () => decodeURIComponent(currentCode));
42+
} else if (AppConstants.platform === "macosx") {
43+
const { MacAttribution } = ChromeUtils.importESModule(
44+
"resource:///modules/MacAttribution.sys.mjs"
45+
);
46+
47+
await MacAttribution.setAttributionString(currentCode);
4248
} else {
49+
// non-msix windows
4350
await AttributionCode.writeAttributionFile(currentCode);
4451
}
4552
AttributionCode._clearCache();
@@ -80,7 +87,14 @@ add_task(async function testInvalidAttrCodes() {
8087
}
8188

8289
msixCampaignIdStub.callsFake(async () => decodeURIComponent(currentCode));
90+
} else if (AppConstants.platform === "macosx") {
91+
const { MacAttribution } = ChromeUtils.importESModule(
92+
"resource:///modules/MacAttribution.sys.mjs"
93+
);
94+
95+
await MacAttribution.setAttributionString(currentCode);
8396
} else {
97+
// non-msix windows
8498
await AttributionCode.writeAttributionFile(currentCode);
8599
}
86100
AttributionCode._clearCache();
@@ -102,11 +116,12 @@ add_task(async function testInvalidAttrCodes() {
102116
* and making sure we still get the expected code.
103117
*/
104118
let condition = {
105-
// MSIX attribution codes are not cached by us, thus this test is
119+
// macOS and MSIX attribution codes are not cached by us, thus this test is
106120
// unnecessary for those builds.
107121
skip_if: () =>
108-
AppConstants.platform === "win" &&
109-
Services.sysinfo.getProperty("hasWinPackageId"),
122+
(AppConstants.platform === "win" &&
123+
Services.sysinfo.getProperty("hasWinPackageId")) ||
124+
AppConstants.platform === "macosx",
110125
};
111126
add_task(condition, async function testDeletedFile() {
112127
// Set up the test by clearing the cache and writing a valid file.

0 commit comments

Comments
 (0)