fo(info) {
    const get = name => {
      try {
        return info[name];
      } catch (e) {}
      return undefined;
    };

    const clean = rawObj => {
      const obj = JSON.parse(JSON.stringify(rawObj));
      if (!Object.keys(obj).length) {
        return undefined;
      }
      return obj;
    };

    const cleanDevice = (vendorID, deviceID, subsysID) => {
      return clean({ vendorID, deviceID, subsysID });
    };

    const d1 = cleanDevice(
      get("adapterVendorID"),
      get("adapterDeviceID"),
      get("adapterSubsysID")
    );
    const d2 = cleanDevice(
      get("adapterVendorID2"),
      get("adapterDeviceID2"),
      get("adapterSubsysID2")
    );
    const devices = (get("isGPU2Active") ? [d2, d1] : [d1, d2]).filter(
      v => v !== undefined
    );

    return clean({
      directWriteEnabled: get("directWriteEnabled"),
      directWriteVersion: get("directWriteVersion"),
      hasTouchScreen: info.ApzTouchInput == 1,
      clearTypeParameters: get("clearTypeParameters"),
      targetFrameRate: get("targetFrameRate"),
      devices,
    });
  }

  #parseCodecSupportInfo(codecSupportInfo) {
    if (!codecSupportInfo) {
      return undefined;
    }

    const codecs = {};
    for (const item of codecSupportInfo.split("\n")) {
      const [codec, ...types] = item.split(" ");
      if (!codecs[codec]) {
        codecs[codec] = {
          hardwareDecode: false,
          softwareDecode: false,
          hardwareEncode: false,
          softwareEncode: false,
        };
      }
      codecs[codec].softwareDecode ||= types.includes("SWDEC");
      codecs[codec].hardwareDecode ||= types.includes("HWDEC");
      codecs[codec].softwareEncode ||= types.includes("SWENC");
      codecs[codec].hardwareEncode ||= types.includes("HWENC");
    }
    return codecs;
  }

  #parseFeatureLog(featureLog = {}) {
    const { features } = featureLog;
    if (!features) {
      return undefined;
    }

    const parsedFeatures = {};
    for (let { name, log, status } of features) {
      for (const item of log.reverse()) {
        if (!item.failureId || item.status != status) {
          continue;
        }
        status = `${status} (${item.message || item.failureId})`;
      }
      parsedFeatures[name] = status;
    }
    return parsedFeatures;
  }

  #getGraphicsInfo(troubleshoot) {
    const { graphics, media } = troubleshoot;
    const { featureLog } = graphics;
    const data = this.#parseGfxInfo(graphics);
    data.drivers = [
      {
        renderer: graphics.webgl1Renderer,
        version: graphics.webgl1Version,
      },
      {
        renderer: graphics.webgl2Renderer,
        version: graphics.webgl2Version,
      },
    ].filter(({ version }) => version && version != "-");

    data.codecSupport = this.#parseCodecSupportInfo(media.codecSupportInfo);
    data.features = this.#parseFeatureLog(featureLog);

    const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
    data.monitors = gfxInfo.getMonitors();

    return data;
  }

  #getAppInfo(troubleshootingInfo) {
    const { application } = troubleshootingInfo;
    return {
      applicationName: application.name,
      buildId: application.buildID,
      defaultUserAgent: application.userAgent,
      updateChannel: application.updateChannel,
      version: application.version,
    };
  }

  #getSysinfoProperty(propertyName, defaultValue) {
    try {
      return Services.sysinfo.getProperty(propertyName);
    } catch (e) {}
    return defaultValue;
  }

  #getPrefs() {
    const prefs = {};
    for (const name of [
      "layers.acceleration.force-enabled",
      "gfx.webrender.software",
      "browser.opaqueResponseBlocking",
      "extensions.InstallTrigger.enabled",
      "privacy.resistFingerprinting",
      "privacy.globalprivacycontrol.enabled",
      "network.cookie.cookieBehavior.optInPartitioning",
      "network.cookie.cookieBehavior.optInPartitioning.pbmode",
    ]) {
      prefs[name] = Services.prefs.getBoolPref(name, undefined);
    }
    const cookieBehavior = "network.cookie.cookieBehavior";
    prefs[cookieBehavior] = Services.prefs.getIntPref(cookieBehavior, -1);
    return prefs;
  }

  async #getPlatformInfo(troubleshootingInfo) {
    const { application } = troubleshootingInfo;
    const { memorySizeBytes, fissionAutoStart } = application;

    let memoryMB = memorySizeBytes;
    if (memoryMB) {
      memoryMB = Math.round(memoryMB / 1024 / 1024);
    }

    const info = {
      fissionEnabled: fissionAutoStart,
      memoryMB,
      osArchitecture: this.#getSysinfoProperty("arch", null),
      osName: this.#getSysinfoProperty("name", null),
      osVersion: this.#getSysinfoProperty("version", null),
      name: AppConstants.platform,
    };
    if (info.os === "android") {
      info.device = this.#getSysinfoProperty("device", null);
      info.isTablet = this.#getSysinfoProperty("tablet", false);
    }
    if (
      info.osName == "Windows_NT" &&
      (await Services.sysinfo.processInfo).isWindowsSMode
    ) {
      info.osVersion += " S";
    }
    return info;
  }

  #getSecurityInfo(troubleshootingInfo) {
    const result = {};
    for (const [k, v] of Object.entries(troubleshootingInfo.securitySoftware)) {
      result[k.replace("registered", "").toLowerCase()] = v
        ? v.split(";")
        : null;
    }

    // Right now, security data is only available for Windows builds, and
    // we might as well not return anything at all if no data is available.
    if (!Object.values(result).filter(e => e).length) {
      return undefined;
    }

    return result;
  }

  static AUTOMATION_ADDON_IDS = [
    "mochikit@mozilla.org",
    "special-powers@mozilla.org",
  ];

  static WANTED_ADDON_LOCATIONS = ["app-profile", "app-temporary"];

  #getActiveAddons(troubleshootingInfo) {
    const { addons } = troubleshootingInfo;
    if (!addons) {
      return [];
    }
    // We only care about enabled addons (not themes) the user
    // installed, not ones bundled with Firefox.
    const toReport = addons.filter(
      ({ id, isActive, type, locationName }) =>
        (!Cu.isInAutomation ||
          !ReportBrokenSiteParent.AUTOMATION_ADDON_IDS.includes(id)) &&
        isActive &&
        type === "extension" &&
        ReportBrokenSiteParent.WANTED_ADDON_LOCATIONS.includes(locationName)
    );
    return toReport.map(({ id, name, version, locationName }) => {
      return {
        id,
        name,
        temporary: locationName === "app-temporary",
        version,
      };
    });
  }

  #getActiveExperiments(troubleshootingInfo) {
    if (!troubleshootingInfo?.normandy) {
      return [];
    }
    const {
      normandy: { nimbusExperiments, nimbusRollouts },
    } = troubleshootingInfo;
    return [
      nimbusExperiments.map(({ slug, branch }) => {
        return { slug, branch: branch.slug, kind: "nimbusExperiment" };
      }),
      nimbusRollouts.map(({ slug, branch }) => {
        return { slug, branch: branch.slug, kind: "nimbusRollout" };
      }),
    ]
      .flat()
      .sort((a, b) => a.slug.localeCompare(b.slug));
  }

  async #getBrowserInfo() {
    const troubleshootingInfo = await Troubleshoot.snapshot();
    return {
      addons: this.#getActiveAddons(troubleshootingInfo),
      app: this.#getAppInfo(troubleshootingInfo),
      experiments: this.#getActiveExperiments(troubleshootingInfo),
      graphics: this.#getGraphicsInfo(troubleshootingInfo),
      locales: troubleshootingInfo.intl.localeService.available,
      prefs: this.#getPrefs(),
      platform: await this.#getPlatformInfo(troubleshootingInfo),
      security: this.#getSecurityInfo(troubleshootingInfo),
    };
  }

  async #getScreenshot(browsingContext, format, quality) {
    const zoom = browsingContext.fullZoom;
    const scale = browsingContext.topChromeWindow?.devicePixelRatio || 1;
    const wgp = browsingContext.currentWindowGlobal;

    const image = await wgp.drawSnapshot(
      undefined, // rect
      scale * zoom,
      "white",
      undefined // resetScrollPosition
    );

    const canvas = new OffscreenCanvas(image.width, image.height);

    const ctx = canvas.getContext("bitmaprenderer", { alpha: false });
    ctx.transferFromImageBitmap(image);

    const blob = await canvas.convertToBlob({
      type: `image/${format}`,
      quality: quality / 100,
    });

    const dataURL = await new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = () => reject(reader.error);
      reader.readAsDataURL(blob);
    });

    return dataURL;
  }

  async receiveMessage(msg) {
    switch (msg.name) {
      case "GetWebcompatInfoFromParentProcess": {
        const { browsingContext } = msg.target;
        const { format, quality } = msg.data;
        const screenshot = await this.#getScreenshot(
          browsingContext,
          format,
          quality
        ).catch(e => {
          console.error("Report Broken Site: getting a screenshot failed", e);
          return Promise.resolve(undefined);
        });

        const zoom = browsingContext.fullZoom;
        const scale = browsingContext.topChromeWindow?.devicePixelRatio || 1;
        const devicePixelRatio = scale * zoom;

        return {
          antitracking: this.#getAntitrackingInfo(msg.target.browsingContext),
          browser: await this.#getBrowserInfo(),
          devicePixelRatio,
          screenshot,
        };
      }
    }
    return null;
  }
}
PK