@@ -16,6 +16,7 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
16
16
const lazy = { } ;
17
17
ChromeUtils . defineESModuleGetters ( lazy , {
18
18
MacAttribution : "resource:///modules/MacAttribution.sys.mjs" ,
19
+ UpdateUtils : "resource://gre/modules/UpdateUtils.sys.mjs" ,
19
20
} ) ;
20
21
ChromeUtils . defineLazyGetter ( lazy , "log" , ( ) => {
21
22
let { ConsoleAPI } = ChromeUtils . importESModule (
@@ -81,6 +82,49 @@ export var AttributionCode = {
81
82
let file = Services . dirsvc . get ( "GreD" , Ci . nsIFile ) ;
82
83
file . append ( "postSigningData" ) ;
83
84
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 ;
84
128
}
85
129
86
130
return null ;
@@ -91,8 +135,8 @@ export var AttributionCode = {
91
135
* @param {String } code to write.
92
136
*/
93
137
async writeAttributionFile ( code ) {
94
- // Writing attribution files is only used as part of test code
95
- // so bailing here for MSIX builds is no big deal.
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.
96
140
if (
97
141
AppConstants . platform === "win" &&
98
142
Services . sysinfo . getProperty ( "hasWinPackageId" )
@@ -183,58 +227,6 @@ export var AttributionCode = {
183
227
return s ;
184
228
} ,
185
229
186
- async _getMacAttrDataAsync ( ) {
187
- // On macOS, we fish the attribution data from an extended attribute on
188
- // the .app bundle directory.
189
- try {
190
- let attrStr = await lazy . MacAttribution . getAttributionString ( ) ;
191
- lazy . log . debug (
192
- `_getMacAttrDataAsync: getAttributionString: "${ attrStr } "`
193
- ) ;
194
-
195
- gCachedAttrData = this . parseAttributionCode ( attrStr ) ;
196
- } catch ( ex ) {
197
- // Avoid partial attribution data.
198
- gCachedAttrData = { } ;
199
-
200
- // No attributions. Just `warn` 'cuz this isn't necessarily an error.
201
- lazy . log . warn ( "Caught exception fetching macOS attribution codes!" , ex ) ;
202
-
203
- if (
204
- ex instanceof Ci . nsIException &&
205
- ex . result == Cr . NS_ERROR_UNEXPECTED
206
- ) {
207
- // Bad quarantine data.
208
- Services . telemetry
209
- . getHistogramById ( "BROWSER_ATTRIBUTION_ERRORS" )
210
- . add ( "quarantine_error" ) ;
211
- }
212
- }
213
-
214
- lazy . log . debug (
215
- `macOS attribution data is ${ JSON . stringify ( gCachedAttrData ) } `
216
- ) ;
217
-
218
- return gCachedAttrData ;
219
- } ,
220
-
221
- async _getWindowsNSISAttrDataAsync ( ) {
222
- return AttributionIOUtils . read ( this . attributionFile . path ) ;
223
- } ,
224
-
225
- async _getWindowsMSIXAttrDataAsync ( ) {
226
- // This comes out of windows-package-manager _not_ URL encoded or in an ArrayBuffer,
227
- // but the parsing code wants it that way. It's easier to just provide that
228
- // than have the parsing code support both.
229
- lazy . log . debug (
230
- `winPackageFamilyName is: ${ Services . sysinfo . getProperty (
231
- "winPackageFamilyName"
232
- ) } `
233
- ) ;
234
- let encoder = new TextEncoder ( ) ;
235
- return encoder . encode ( encodeURIComponent ( await this . msixCampaignId ) ) ;
236
- } ,
237
-
238
230
/**
239
231
* Reads the attribution code, either from disk or a cached version.
240
232
* Returns a promise that fulfills with an object containing the parsed
@@ -246,10 +238,6 @@ export var AttributionCode = {
246
238
* strip "utm_" while retrieving the params.
247
239
*/
248
240
async getAttrDataAsync ( ) {
249
- if ( AppConstants . platform != "win" && AppConstants . platform != "macosx" ) {
250
- // This platform doesn't support attribution.
251
- return gCachedAttrData ;
252
- }
253
241
if ( gCachedAttrData != null ) {
254
242
lazy . log . debug (
255
243
`getAttrDataAsync: attribution is cached: ${ JSON . stringify (
@@ -271,26 +259,98 @@ export var AttributionCode = {
271
259
}
272
260
273
261
gCachedAttrData = { } ;
262
+ let attributionFile = this . attributionFile ;
263
+ if ( ! attributionFile ) {
264
+ // This platform doesn't support attribution.
265
+ lazy . log . debug (
266
+ `getAttrDataAsync: no attribution (attributionFile is null)`
267
+ ) ;
268
+ return gCachedAttrData ;
269
+ }
274
270
275
- if ( AppConstants . platform == "macosx" ) {
276
- lazy . log . debug ( `getAttrDataAsync: macOS` ) ;
277
- return this . _getMacAttrDataAsync ( ) ;
271
+ if (
272
+ AppConstants . platform == "macosx" &&
273
+ ! ( await AttributionIOUtils . exists ( attributionFile . path ) )
274
+ ) {
275
+ lazy . log . debug (
276
+ `getAttrDataAsync: macOS && !exists("${ attributionFile . path } ")`
277
+ ) ;
278
+
279
+ // On macOS, we fish the attribution data from an extended attribute on
280
+ // the .app bundle directory.
281
+ try {
282
+ let attrStr = await lazy . MacAttribution . getAttributionString ( ) ;
283
+ lazy . log . debug (
284
+ `getAttrDataAsync: macOS attribution getAttributionString: "${ attrStr } "`
285
+ ) ;
286
+
287
+ gCachedAttrData = this . parseAttributionCode ( attrStr ) ;
288
+ } catch ( ex ) {
289
+ // Avoid partial attribution data.
290
+ gCachedAttrData = { } ;
291
+
292
+ // No attributions. Just `warn` 'cuz this isn't necessarily an error.
293
+ lazy . log . warn ( "Caught exception fetching macOS attribution codes!" , ex ) ;
294
+
295
+ if (
296
+ ex instanceof Ci . nsIException &&
297
+ ex . result == Cr . NS_ERROR_UNEXPECTED
298
+ ) {
299
+ // Bad quarantine data.
300
+ Services . telemetry
301
+ . getHistogramById ( "BROWSER_ATTRIBUTION_ERRORS" )
302
+ . add ( "quarantine_error" ) ;
303
+ }
304
+ }
305
+
306
+ lazy . log . debug (
307
+ `macOS attribution data is ${ JSON . stringify ( gCachedAttrData ) } `
308
+ ) ;
309
+
310
+ // We only want to try to fetch the attribution string once on macOS
311
+ try {
312
+ let code = this . serializeAttributionData ( gCachedAttrData ) ;
313
+ lazy . log . debug ( `macOS attribution data serializes as "${ code } "` ) ;
314
+ await this . writeAttributionFile ( code ) ;
315
+ } catch ( ex ) {
316
+ lazy . log . debug (
317
+ `Caught exception writing "${ attributionFile . path } "` ,
318
+ ex
319
+ ) ;
320
+ Services . telemetry
321
+ . getHistogramById ( "BROWSER_ATTRIBUTION_ERRORS" )
322
+ . add ( "write_error" ) ;
323
+ return gCachedAttrData ;
324
+ }
325
+
326
+ lazy . log . debug (
327
+ `Returning after successfully writing "${ attributionFile . path } "`
328
+ ) ;
329
+ return gCachedAttrData ;
278
330
}
279
331
280
- lazy . log . debug ( "getAttrDataAsync: !macOS" ) ;
332
+ lazy . log . debug (
333
+ `getAttrDataAsync: !macOS || !exists("${ attributionFile . path } ")`
334
+ ) ;
281
335
282
- let attributionFile = this . attributionFile ;
283
336
let bytes ;
284
337
try {
285
338
if (
286
339
AppConstants . platform === "win" &&
287
340
Services . sysinfo . getProperty ( "hasWinPackageId" )
288
341
) {
289
- lazy . log . debug ( "getAttrDataAsync: MSIX" ) ;
290
- bytes = await this . _getWindowsMSIXAttrDataAsync ( ) ;
342
+ // This comes out of windows-package-manager _not_ URL encoded or in an ArrayBuffer,
343
+ // but the parsing code wants it that way. It's easier to just provide that
344
+ // than have the parsing code support both.
345
+ lazy . log . debug (
346
+ `winPackageFamilyName is: ${ Services . sysinfo . getProperty (
347
+ "winPackageFamilyName"
348
+ ) } `
349
+ ) ;
350
+ let encoder = new TextEncoder ( ) ;
351
+ bytes = encoder . encode ( encodeURIComponent ( await this . msixCampaignId ( ) ) ) ;
291
352
} else {
292
- lazy . log . debug ( "getAttrDataAsync: NSIS" ) ;
293
- bytes = await this . _getWindowsNSISAttrDataAsync ( ) ;
353
+ bytes = await AttributionIOUtils . read ( attributionFile . path ) ;
294
354
}
295
355
} catch ( ex ) {
296
356
if ( DOMException . isInstance ( ex ) && ex . name == "NotFoundError" ) {
@@ -359,15 +419,12 @@ export var AttributionCode = {
359
419
* or if the file couldn't be deleted (the promise is never rejected).
360
420
*/
361
421
async deleteFileAsync ( ) {
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
- }
422
+ try {
423
+ await IOUtils . remove ( this . attributionFile . path ) ;
424
+ } catch ( ex ) {
425
+ // The attribution file may already have been deleted,
426
+ // or it may have never been installed at all;
427
+ // failure to delete it isn't an error.
371
428
}
372
429
} ,
373
430
0 commit comments