forked from mirrors/gecko-dev
		
	 4be49e117e
			
		
	
	
		4be49e117e
		
	
	
	
	
		
			
			We can't add the provenance data to the `installation.first_seen` extra data because it is already at its maximum number of keys. So instead we will add the `installation.first_seen_prov_ext` event which will be sent at the same time as `installation.first_seen` and will contain provenance attribution data in its extras object. Differential Revision: https://phabricator.services.mozilla.com/D172520
		
			
				
	
	
		
			544 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			544 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   AttributionIOUtils: "resource:///modules/AttributionCode.sys.mjs",
 | |
| });
 | |
| 
 | |
| let gReadZoneIdPromise = null;
 | |
| let gTelemetryPromise = null;
 | |
| 
 | |
| export var ProvenanceData = {
 | |
|   /**
 | |
|    * Clears cached code/Promises. For testing only.
 | |
|    */
 | |
|   _clearCache() {
 | |
|     if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
 | |
|       gReadZoneIdPromise = null;
 | |
|       gTelemetryPromise = null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns an nsIFile for the file containing the zone identifier-based
 | |
|    * provenance data. This currently only exists on Windows. On other platforms,
 | |
|    * this will return null.
 | |
|    */
 | |
|   get zoneIdProvenanceFile() {
 | |
|     if (AppConstants.platform == "win") {
 | |
|       let file = Services.dirsvc.get("GreD", Ci.nsIFile);
 | |
|       file.append("zoneIdProvenanceData");
 | |
|       return file;
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Loads the provenance data that the installer copied (along with some
 | |
|    * metadata) from the Firefox installer used to create the current
 | |
|    * installation.
 | |
|    *
 | |
|    * If it doesn't already exist, creates a global Promise that loads the data
 | |
|    * from the file and caches it. Subsequent calls get the same Promise. There
 | |
|    * is no support for re-reading the file from the disk because there is no
 | |
|    * good reason that the contents of the file should change.
 | |
|    *
 | |
|    * Only expected contents will be pulled out of the file. This does not
 | |
|    * extract arbitrary, unexpected data. Data will be validated, to the extent
 | |
|    * possible. Most possible data returned has a potential key indicating that
 | |
|    * we read an unexpected value out of the file.
 | |
|    *
 | |
|    * @returns `null` on unsupported OSs. Otherwise, an object with these
 | |
|    *          possible keys:
 | |
|    *    `readProvenanceError`
 | |
|    *      Will be present if there was a problem when Firefox tried to read the
 | |
|    *      file that ought to have been written by the installer. Possible values
 | |
|    *      are:
 | |
|    *        `noSuchFile`, `readError`, `parseError`
 | |
|    *      If this key is present, no other keys will be present.
 | |
|    *    `fileSystem`
 | |
|    *      What filesystem the installer was on. If available, this will be a
 | |
|    *      string returned from `GetVolumeInformationByHandleW` via its
 | |
|    *      `lpFileSystemNameBuffer` parameter. Possible values are:
 | |
|    *        `NTFS`, `FAT32`, `other`, `missing`, `readIniError`
 | |
|    *      `other` will be used if the value read does not match one of the
 | |
|    *      expected file systems.
 | |
|    *      `missing` will be used if the file didn't contain `fileSystem` or
 | |
|    *      `readFsError` information.
 | |
|    *      `readIniError` will be used if an error is encountered when reading
 | |
|    *      the key from the file in the installation directory.
 | |
|    *    `readFsError`
 | |
|    *      The reason why the installer file system could not be determined. Will
 | |
|    *      be present if `readProvenanceError` and `fileSystem` are not. Possible
 | |
|    *      values are:
 | |
|    *        `openFile`, `getVolInfo`, `fsUnterminated`, `getBufferSize`,
 | |
|    *        `convertString`, `unexpected`, `readIniError`
 | |
|    *      `unexpected` will be used if the value read from the file didn't match
 | |
|    *      any of the expected values, for some reason.
 | |
|    *      `missing` will be used if the file didn't contain `fileSystem` or
 | |
|    *      `readFsError` information.
 | |
|    *      `readIniError` will be used if an error is encountered when reading
 | |
|    *      the key from the file in the installation directory.
 | |
|    *    `readFsErrorCode`
 | |
|    *      An integer returned by `GetLastError()` indicating, in more detail,
 | |
|    *      why we failed to obtain the file system. This key may exist if
 | |
|    *      `readFsError` exists.
 | |
|    *    `readZoneIdError`
 | |
|    *      The reason why the installer was unable to read its zone identifier
 | |
|    *      ADS. Possible values are:
 | |
|    *        `openFile`, `readFile`, `unexpected`, `readIniError`
 | |
|    *      `unexpected` will be used if the value read from the file didn't match
 | |
|    *      any of the expected values, for some reason.
 | |
|    *      `readIniError` will be used if an error is encountered when reading
 | |
|    *      the key from the file in the installation directory.
 | |
|    *    `readZoneIdErrorCode`
 | |
|    *      An integer returned by `GetLastError()` indicating, in more detail,
 | |
|    *      why we failed to read the zone identifier ADS. This key may exist if
 | |
|    *      `readZoneIdError` exists.
 | |
|    *    `zoneIdFileSize`
 | |
|    *      This key should exist if Firefox successfully read the file in the
 | |
|    *      installation directory and the installer successfully opened the ADS.
 | |
|    *      If the installer failed to get the size of the ADS prior to reading
 | |
|    *      it, this will be `unknown`. If the installer was able to get the ADS
 | |
|    *      size, this will be an integer describing how many bytes long it was.
 | |
|    *      If this value in installation directory's file isn't `unknown` or an
 | |
|    *      integer, this will be `unexpected`. If an error is encountered when
 | |
|    *      reading the key from the file in the installation directory, this will
 | |
|    *      be `readIniError`.
 | |
|    *    `zoneIdBufferLargeEnough`
 | |
|    *      This key should exist if Firefox successfully read the file in the
 | |
|    *      installation directory and the installer successfully opened the ADS.
 | |
|    *      Indicates whether the zone identifier ADS size was bigger than the
 | |
|    *      maximum size that the installer will read from it. If we failed to
 | |
|    *      determine the ADS size, this will be `unknown`. If the installation
 | |
|    *      directory's file contains an invalid value, this will be `unexpected`.
 | |
|    *      If an error is encountered when reading the key from the file in the
 | |
|    *      installation directory, this will be `readIniError`.
 | |
|    *      Otherwise, this will be a boolean indicating whether or not the buffer
 | |
|    *      was large enough to fit the ADS data into.
 | |
|    *    `zoneIdTruncated`
 | |
|    *      This key should exist if Firefox successfully read the file in the
 | |
|    *      installation directory and the installer successfully read the ADS.
 | |
|    *      Indicates whether or not we read through the end of the ADS data when
 | |
|    *      we copied it. If the installer failed to determine this, this value
 | |
|    *      will be `unknown`. If the installation directory's file contains an
 | |
|    *      invalid value, this will be `unexpected`. If an error is encountered
 | |
|    *      when reading the key from the file in the installation directory, this
 | |
|    *      will be `readIniError`. Otherwise, this will be a boolean value
 | |
|    *      indicating whether or not the data that we copied was truncated.
 | |
|    *    `zoneId`
 | |
|    *      The Security Zone that the Zone Identifier data indicates that
 | |
|    *      installer was downloaded from. See this documentation:
 | |
|    *      https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms537183(v=vs.85)
 | |
|    *      This key will be present if `readProvenanceError` and
 | |
|    *      `readZoneIdError` are not. It will either be a valid zone ID (an
 | |
|    *      integer between 0 and 4, inclusive), or else it will be `unexpected`,
 | |
|    *      `missing`, or `readIniError`.
 | |
|    *    `referrerUrl`
 | |
|    *      The URL of the download referrer. This key will be present if
 | |
|    *      `readProvenanceError` and `readZoneIdError` are not. It will either be
 | |
|    *      a `URL` object, or else it will be `unexpected`, `missing`, or
 | |
|    *      `readIniError`.
 | |
|    *    `referrerUrlIsMozilla`
 | |
|    *      This key will be present if `ReferrerUrl` is a `URL` object. It will
 | |
|    *      be `true`` if the download referrer appears to be a Mozilla URL.
 | |
|    *      Otherwise it will be `false`.
 | |
|    *    `hostUrl`
 | |
|    *      The URL of the download source. This key will be present if
 | |
|    *      `readProvenanceError` and `readZoneIdError` are not. It will either be
 | |
|    *      a `URL` object, or else it will be `unexpected`, `missing`, or
 | |
|    *      `readIniError`
 | |
|    *    `hostUrlIsMozilla`
 | |
|    *      This key will be present if `HostUrl` is a `URL` object. It will
 | |
|    *      be `true`` if the download source appears to be a Mozilla URL.
 | |
|    *      Otherwise it will be `false`.
 | |
|    */
 | |
|   async readZoneIdProvenanceFile() {
 | |
|     if (gReadZoneIdPromise) {
 | |
|       return gReadZoneIdPromise;
 | |
|     }
 | |
|     gReadZoneIdPromise = (async () => {
 | |
|       let file = this.zoneIdProvenanceFile;
 | |
|       if (!file) {
 | |
|         return null;
 | |
|       }
 | |
|       let iniData;
 | |
|       try {
 | |
|         iniData = await lazy.AttributionIOUtils.readUTF8(file.path);
 | |
|       } catch (ex) {
 | |
|         if (DOMException.isInstance(ex) && ex.name == "NotFoundError") {
 | |
|           return { readProvenanceError: "noSuchFile" };
 | |
|         }
 | |
|         return { readProvenanceError: "readError" };
 | |
|       }
 | |
| 
 | |
|       let ini;
 | |
|       try {
 | |
|         // We would rather use asynchronous I/O, so we are going to read the
 | |
|         // file with IOUtils and then pass the result into the INI parser
 | |
|         // rather than just giving the INI parser factory the file.
 | |
|         ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
 | |
|           .getService(Ci.nsIINIParserFactory)
 | |
|           .createINIParser(null);
 | |
|         ini.initFromString(iniData);
 | |
|       } catch (ex) {
 | |
|         return { readProvenanceError: "parseError" };
 | |
|       }
 | |
| 
 | |
|       const unexpectedValueError = "unexpected";
 | |
|       const missingKeyError = "missing";
 | |
|       const readIniError = "readIniError";
 | |
|       const possibleIniErrors = [missingKeyError, readIniError];
 | |
| 
 | |
|       // Format is {"IniSectionName": {"IniKeyName": "IniValue"}}
 | |
|       let iniValues = {
 | |
|         Mozilla: {
 | |
|           fileSystem: null,
 | |
|           readFsError: null,
 | |
|           readFsErrorCode: null,
 | |
|           readZoneIdError: null,
 | |
|           readZoneIdErrorCode: null,
 | |
|           zoneIdFileSize: null,
 | |
|           zoneIdBufferLargeEnough: null,
 | |
|           zoneIdTruncated: null,
 | |
|         },
 | |
|         ZoneTransfer: {
 | |
|           ZoneId: null,
 | |
|           ReferrerUrl: null,
 | |
|           HostUrl: null,
 | |
|         },
 | |
|       };
 | |
| 
 | |
|       // The ini reader interface is a little weird in that if we just try to
 | |
|       // read a value from a known section/key and the section/key doesn't
 | |
|       // exist, we just get a generic error rather than an indication that
 | |
|       // the section/key doesn't exist. To distinguish missing keys from any
 | |
|       // other potential errors, we are going to enumerate the sections and
 | |
|       // keys use that to determine if we should try to read from them.
 | |
|       let existingSections;
 | |
|       try {
 | |
|         existingSections = Array.from(ini.getSections());
 | |
|       } catch (ex) {
 | |
|         return { readProvenanceError: "parseError" };
 | |
|       }
 | |
|       for (const section in iniValues) {
 | |
|         if (!existingSections.includes(section)) {
 | |
|           for (const key in iniValues[section]) {
 | |
|             iniValues[section][key] = missingKeyError;
 | |
|           }
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         let existingKeys;
 | |
|         try {
 | |
|           existingKeys = Array.from(ini.getKeys(section));
 | |
|         } catch (ex) {
 | |
|           for (const key in iniValues[section]) {
 | |
|             iniValues[section][key] = readIniError;
 | |
|           }
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         for (const key in iniValues[section]) {
 | |
|           if (!existingKeys.includes(key)) {
 | |
|             iniValues[section][key] = missingKeyError;
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           let value;
 | |
|           try {
 | |
|             value = ini.getString(section, key).trim();
 | |
|           } catch (ex) {
 | |
|             value = readIniError;
 | |
|           }
 | |
|           iniValues[section][key] = value;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // This helps with how verbose the validation gets.
 | |
|       const fileSystem = iniValues.Mozilla.fileSystem;
 | |
|       const readFsError = iniValues.Mozilla.readFsError;
 | |
|       const readFsErrorCode = iniValues.Mozilla.readFsErrorCode;
 | |
|       const readZoneIdError = iniValues.Mozilla.readZoneIdError;
 | |
|       const readZoneIdErrorCode = iniValues.Mozilla.readZoneIdErrorCode;
 | |
|       const zoneIdFileSize = iniValues.Mozilla.zoneIdFileSize;
 | |
|       const zoneIdBufferLargeEnough = iniValues.Mozilla.zoneIdBufferLargeEnough;
 | |
|       const zoneIdTruncated = iniValues.Mozilla.zoneIdTruncated;
 | |
|       const zoneId = iniValues.ZoneTransfer.ZoneId;
 | |
|       const referrerUrl = iniValues.ZoneTransfer.ReferrerUrl;
 | |
|       const hostUrl = iniValues.ZoneTransfer.HostUrl;
 | |
| 
 | |
|       let returnObject = {};
 | |
| 
 | |
|       // readFsError, readFsErrorCode, fileSystem
 | |
|       const validReadFsErrors = [
 | |
|         "openFile",
 | |
|         "getVolInfo",
 | |
|         "fsUnterminated",
 | |
|         "getBufferSize",
 | |
|         "convertString",
 | |
|       ];
 | |
|       // These must be upper case
 | |
|       const validFileSystemValues = ["NTFS", "FAT32"];
 | |
|       if (fileSystem == missingKeyError && readFsError != missingKeyError) {
 | |
|         if (
 | |
|           possibleIniErrors.includes(readFsError) ||
 | |
|           validReadFsErrors.includes(readFsError)
 | |
|         ) {
 | |
|           returnObject.readFsError = readFsError;
 | |
|         } else {
 | |
|           returnObject.readFsError = unexpectedValueError;
 | |
|         }
 | |
|         if (readFsErrorCode != missingKeyError) {
 | |
|           let code = parseInt(readFsErrorCode, 10);
 | |
|           if (!isNaN(code)) {
 | |
|             returnObject.readFsErrorCode = code;
 | |
|           }
 | |
|         }
 | |
|       } else if (possibleIniErrors.includes(fileSystem)) {
 | |
|         returnObject.fileSystem = fileSystem;
 | |
|       } else if (validFileSystemValues.includes(fileSystem.toUpperCase())) {
 | |
|         returnObject.fileSystem = fileSystem.toUpperCase();
 | |
|       } else {
 | |
|         returnObject.fileSystem = "other";
 | |
|       }
 | |
| 
 | |
|       // zoneIdFileSize
 | |
|       if (zoneIdFileSize == missingKeyError) {
 | |
|         // We don't include this one if it's missing.
 | |
|       } else if (
 | |
|         zoneIdFileSize == readIniError ||
 | |
|         zoneIdFileSize == "unknown"
 | |
|       ) {
 | |
|         returnObject.zoneIdFileSize = zoneIdFileSize;
 | |
|       } else {
 | |
|         let size = parseInt(zoneIdFileSize, 10);
 | |
|         if (isNaN(size)) {
 | |
|           returnObject.zoneIdFileSize = unexpectedValueError;
 | |
|         } else {
 | |
|           returnObject.zoneIdFileSize = size;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       // zoneIdBufferLargeEnough
 | |
|       if (zoneIdBufferLargeEnough == missingKeyError) {
 | |
|         // We don't include this one if it's missing.
 | |
|       } else if (
 | |
|         zoneIdBufferLargeEnough == readIniError ||
 | |
|         zoneIdBufferLargeEnough == "unknown"
 | |
|       ) {
 | |
|         returnObject.zoneIdBufferLargeEnough = zoneIdBufferLargeEnough;
 | |
|       } else if (zoneIdBufferLargeEnough.toLowerCase() == "true") {
 | |
|         returnObject.zoneIdBufferLargeEnough = true;
 | |
|       } else if (zoneIdBufferLargeEnough.toLowerCase() == "false") {
 | |
|         returnObject.zoneIdBufferLargeEnough = false;
 | |
|       } else {
 | |
|         returnObject.zoneIdBufferLargeEnough = unexpectedValueError;
 | |
|       }
 | |
| 
 | |
|       // zoneIdTruncated
 | |
|       if (zoneIdTruncated == missingKeyError) {
 | |
|         // We don't include this one if it's missing.
 | |
|       } else if (
 | |
|         zoneIdTruncated == readIniError ||
 | |
|         zoneIdTruncated == "unknown"
 | |
|       ) {
 | |
|         returnObject.zoneIdTruncated = zoneIdTruncated;
 | |
|       } else if (zoneIdTruncated.toLowerCase() == "true") {
 | |
|         returnObject.zoneIdTruncated = true;
 | |
|       } else if (zoneIdTruncated.toLowerCase() == "false") {
 | |
|         returnObject.zoneIdTruncated = false;
 | |
|       } else {
 | |
|         returnObject.zoneIdTruncated = unexpectedValueError;
 | |
|       }
 | |
| 
 | |
|       // readZoneIdError, readZoneIdErrorCode, zoneId, referrerUrl, hostUrl,
 | |
|       // referrerUrlIsMozilla, hostUrlIsMozilla
 | |
|       const validReadZoneIdErrors = ["openFile", "readFile"];
 | |
|       if (
 | |
|         readZoneIdError != missingKeyError &&
 | |
|         zoneId == missingKeyError &&
 | |
|         referrerUrl == missingKeyError &&
 | |
|         hostUrl == missingKeyError
 | |
|       ) {
 | |
|         if (
 | |
|           possibleIniErrors.includes(readZoneIdError) ||
 | |
|           validReadZoneIdErrors.includes(readZoneIdError)
 | |
|         ) {
 | |
|           returnObject.readZoneIdError = readZoneIdError;
 | |
|         } else {
 | |
|           returnObject.readZoneIdError = unexpectedValueError;
 | |
|         }
 | |
|         if (readZoneIdErrorCode != missingKeyError) {
 | |
|           let code = parseInt(readZoneIdErrorCode, 10);
 | |
|           if (!isNaN(code)) {
 | |
|             returnObject.readZoneIdErrorCode = code;
 | |
|           }
 | |
|         }
 | |
|       } else {
 | |
|         if (possibleIniErrors.includes(zoneId)) {
 | |
|           returnObject.zoneId = zoneId;
 | |
|         } else {
 | |
|           let id = parseInt(zoneId, 10);
 | |
|           if (isNaN(id) || id < 0 || id > 4) {
 | |
|             returnObject.zoneId = unexpectedValueError;
 | |
|           } else {
 | |
|             returnObject.zoneId = id;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         let isMozillaURL = url => {
 | |
|           const mozillaDomains = ["mozilla.com", "mozilla.net", "mozilla.org"];
 | |
|           for (const domain of mozillaDomains) {
 | |
|             if (url.hostname == domain) {
 | |
|               return true;
 | |
|             }
 | |
|             if (url.hostname.endsWith("." + domain)) {
 | |
|               return true;
 | |
|             }
 | |
|           }
 | |
|           return false;
 | |
|         };
 | |
| 
 | |
|         if (possibleIniErrors.includes(referrerUrl)) {
 | |
|           returnObject.referrerUrl = referrerUrl;
 | |
|         } else {
 | |
|           try {
 | |
|             returnObject.referrerUrl = new URL(referrerUrl);
 | |
|           } catch (ex) {
 | |
|             returnObject.referrerUrl = unexpectedValueError;
 | |
|           }
 | |
|           if (URL.isInstance(returnObject.referrerUrl)) {
 | |
|             returnObject.referrerUrlIsMozilla = isMozillaURL(
 | |
|               returnObject.referrerUrl
 | |
|             );
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (possibleIniErrors.includes(hostUrl)) {
 | |
|           returnObject.hostUrl = hostUrl;
 | |
|         } else {
 | |
|           try {
 | |
|             returnObject.hostUrl = new URL(hostUrl);
 | |
|           } catch (ex) {
 | |
|             returnObject.hostUrl = unexpectedValueError;
 | |
|           }
 | |
|           if (URL.isInstance(returnObject.hostUrl)) {
 | |
|             returnObject.hostUrlIsMozilla = isMozillaURL(returnObject.hostUrl);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return returnObject;
 | |
|     })();
 | |
|     return gReadZoneIdPromise;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Only submits telemetry once, no matter how many times it is called.
 | |
|    * Has no effect on OSs where provenance data is not supported.
 | |
|    *
 | |
|    * @returns An object indicating the values submitted. Keys may not match the
 | |
|    *          Scalar names since the returned object is intended to be suitable
 | |
|    *          for use as a Telemetry Event's `extra` object, which has shorter
 | |
|    *          limits for extra key names than the limits for Scalar names.
 | |
|    *          Values will be converted to strings since Telemetry Event's
 | |
|    *          `extra` objects must have string values.
 | |
|    *          On platforms that do not support provenance data, this will always
 | |
|    *          return an empty object.
 | |
|    */
 | |
|   async submitProvenanceTelemetry() {
 | |
|     if (gTelemetryPromise) {
 | |
|       return gTelemetryPromise;
 | |
|     }
 | |
|     gTelemetryPromise = (async () => {
 | |
|       const errorValue = "error";
 | |
| 
 | |
|       let extra = {};
 | |
| 
 | |
|       let provenance = await this.readZoneIdProvenanceFile();
 | |
|       if (!provenance) {
 | |
|         return extra;
 | |
|       }
 | |
| 
 | |
|       let setTelemetry = (scalarName, extraKey, value) => {
 | |
|         Services.telemetry.scalarSet(scalarName, value);
 | |
|         extra[extraKey] = value.toString();
 | |
|       };
 | |
| 
 | |
|       setTelemetry(
 | |
|         "attribution.provenance.data_exists",
 | |
|         "data_exists",
 | |
|         !provenance.readProvenanceError
 | |
|       );
 | |
|       if (provenance.readProvenanceError) {
 | |
|         return extra;
 | |
|       }
 | |
| 
 | |
|       setTelemetry(
 | |
|         "attribution.provenance.file_system",
 | |
|         "file_system",
 | |
|         provenance.fileSystem ?? errorValue
 | |
|       );
 | |
| 
 | |
|       // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-#ERROR_FILE_NOT_FOUND
 | |
|       const ERROR_FILE_NOT_FOUND = 2;
 | |
| 
 | |
|       let ads_exists =
 | |
|         !provenance.readProvenanceError &&
 | |
|         !(
 | |
|           provenance.readZoneIdError == "openFile" &&
 | |
|           provenance.readZoneIdErrorCode == ERROR_FILE_NOT_FOUND
 | |
|         );
 | |
|       setTelemetry(
 | |
|         "attribution.provenance.ads_exists",
 | |
|         "ads_exists",
 | |
|         ads_exists
 | |
|       );
 | |
|       if (!ads_exists) {
 | |
|         return extra;
 | |
|       }
 | |
| 
 | |
|       setTelemetry(
 | |
|         "attribution.provenance.security_zone",
 | |
|         "security_zone",
 | |
|         "zoneId" in provenance ? provenance.zoneId.toString() : errorValue
 | |
|       );
 | |
| 
 | |
|       let haveReferrerUrl = URL.isInstance(provenance.referrerUrl);
 | |
|       setTelemetry(
 | |
|         "attribution.provenance.referrer_url_exists",
 | |
|         "refer_url_exist",
 | |
|         haveReferrerUrl
 | |
|       );
 | |
|       if (haveReferrerUrl) {
 | |
|         setTelemetry(
 | |
|           "attribution.provenance.referrer_url_is_mozilla",
 | |
|           "refer_url_moz",
 | |
|           provenance.referrerUrlIsMozilla
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       let haveHostUrl = URL.isInstance(provenance.hostUrl);
 | |
|       setTelemetry(
 | |
|         "attribution.provenance.host_url_exists",
 | |
|         "host_url_exist",
 | |
|         haveHostUrl
 | |
|       );
 | |
|       if (haveHostUrl) {
 | |
|         setTelemetry(
 | |
|           "attribution.provenance.host_url_is_mozilla",
 | |
|           "host_url_moz",
 | |
|           provenance.hostUrlIsMozilla
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       return extra;
 | |
|     })();
 | |
|     return gTelemetryPromise;
 | |
|   },
 | |
| };
 |