mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	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;
 | 
						|
  },
 | 
						|
};
 |