summaryrefslogtreecommitdiffstats
path: root/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs292
1 files changed, 292 insertions, 0 deletions
diff --git a/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs b/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs
new file mode 100644
index 0000000000..d6363a4ee1
--- /dev/null
+++ b/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs
@@ -0,0 +1,292 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* 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";
+
+function getSysinfoProperty(propertyName, defaultValue) {
+ try {
+ return Services.sysinfo.getProperty(propertyName);
+ } catch (e) {}
+ return defaultValue;
+}
+
+function getSecurityInfo() {
+ const keys = [
+ ["registeredAntiVirus", "antivirus"],
+ ["registeredAntiSpyware", "antispyware"],
+ ["registeredFirewall", "firewall"],
+ ];
+
+ let result = {};
+
+ for (let [inKey, outKey] of keys) {
+ const str = getSysinfoProperty(inKey, null);
+ result[outKey] = !str ? null : str.split(";");
+ }
+
+ // 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;
+}
+
+class DriverInfo {
+ constructor(gl, ext) {
+ try {
+ this.extensions = ext.getParameter(ext.EXTENSIONS);
+ } catch (e) {}
+
+ try {
+ this.renderer = ext.getParameter(gl.RENDERER);
+ } catch (e) {}
+
+ try {
+ this.vendor = ext.getParameter(gl.VENDOR);
+ } catch (e) {}
+
+ try {
+ this.version = ext.getParameter(gl.VERSION);
+ } catch (e) {}
+
+ try {
+ this.wsiInfo = ext.getParameter(ext.WSI_INFO);
+ } catch (e) {}
+ }
+
+ equals(info2) {
+ return this.renderer == info2.renderer && this.version == info2.version;
+ }
+
+ static getByType(driver) {
+ const doc = new DOMParser().parseFromString("<html/>", "text/html");
+ const canvas = doc.createElement("canvas");
+ canvas.width = 1;
+ canvas.height = 1;
+ let error;
+ canvas.addEventListener("webglcontextcreationerror", function (e) {
+ error = true;
+ });
+ let gl = null;
+ try {
+ gl = canvas.getContext(driver);
+ } catch (e) {
+ error = true;
+ }
+ if (error || !gl?.getExtension) {
+ return undefined;
+ }
+
+ let ext = null;
+ try {
+ ext = gl.getExtension("MOZ_debug");
+ } catch (e) {}
+ if (!ext) {
+ return undefined;
+ }
+
+ const data = new DriverInfo(gl, ext);
+
+ try {
+ gl.getExtension("WEBGL_lose_context").loseContext();
+ } catch (e) {}
+
+ return data;
+ }
+
+ static getAll() {
+ const drivers = [];
+
+ function tryDriver(type) {
+ const driver = DriverInfo.getByType(type);
+ if (driver) {
+ drivers.push(driver);
+ }
+ }
+
+ tryDriver("webgl");
+ tryDriver("webgl2");
+ tryDriver("webgpu");
+
+ return drivers;
+ }
+}
+
+export class ReportBrokenSiteParent extends JSWindowActorParent {
+ #getAntitrackingBlockList() {
+ // If content-track-digest256 is in the tracking table,
+ // the user has enabled the strict list.
+ const trackingTable = Services.prefs.getCharPref(
+ "urlclassifier.trackingTable"
+ );
+ return trackingTable.includes("content") ? "strict" : "basic";
+ }
+
+ #getAntitrackingInfo(browsingContext) {
+ return {
+ blockList: this.#getAntitrackingBlockList(),
+ isPrivateBrowsing: browsingContext.usePrivateBrowsing,
+ hasTrackingContentBlocked: !!(
+ browsingContext.currentWindowGlobal.contentBlockingEvents &
+ Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT
+ ),
+ hasMixedActiveContentBlocked: !!(
+ browsingContext.secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
+ ),
+ hasMixedDisplayContentBlocked: !!(
+ browsingContext.secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT
+ ),
+ };
+ }
+
+ #getBasicGraphicsInfo(gfxInfo) {
+ const get = name => {
+ try {
+ return gfxInfo[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({
+ direct2DEnabled: get("direct2DEnabled"),
+ directWriteEnabled: get("directWriteEnabled"),
+ directWriteVersion: get("directWriteVersion"),
+ hasTouchScreen: gfxInfo.getInfo().ApzTouchInput == 1,
+ cleartypeParameters: get("clearTypeParameters"),
+ targetFrameRate: get("targetFrameRate"),
+ devices,
+ });
+ }
+
+ #getGraphicsInfo() {
+ const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ const data = this.#getBasicGraphicsInfo(gfxInfo);
+
+ data.drivers = DriverInfo.getAll().map(({ renderer, vendor, version }) => {
+ return { renderer: `${vendor} -- ${renderer}`, version };
+ });
+
+ try {
+ const info = gfxInfo.CodecSupportInfo;
+ if (info) {
+ const codecs = {};
+ for (const item of gfxInfo.CodecSupportInfo.split("\n")) {
+ const [codec, ...types] = item.split(" ");
+ if (!codecs[codec]) {
+ codecs[codec] = { software: false, hardware: false };
+ }
+ if (types.includes("SW")) {
+ codecs[codec].software = true;
+ }
+ if (types.includes("HW")) {
+ codecs[codec].hardware = true;
+ }
+ }
+ data.codecSupport = codecs;
+ }
+ } catch (e) {}
+
+ try {
+ const { features } = gfxInfo.getFeatureLog();
+ data.features = {};
+ 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})`;
+ }
+ data.features[name] = status;
+ }
+ } catch (e) {}
+
+ try {
+ if (AppConstants.platform !== "android") {
+ data.monitors = gfxInfo.getMonitors();
+ }
+ } catch (e) {}
+
+ return data;
+ }
+
+ 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 doc = Services.appShell.hiddenDOMWindow.document;
+ const canvas = doc.createElement("canvas");
+ canvas.width = image.width;
+ canvas.height = image.height;
+
+ const ctx = canvas.getContext("2d", { alpha: false });
+ ctx.drawImage(image, 0, 0);
+ image.close();
+
+ return canvas.toDataURL(`image/${format}`, quality / 100);
+ }
+
+ async receiveMessage(msg) {
+ switch (msg.name) {
+ case "GetWebcompatInfoOnlyAvailableInParentProcess": {
+ const { format, quality } = msg.data;
+ const screenshot = await this.#getScreenshot(
+ msg.target.browsingContext,
+ format,
+ quality
+ ).catch(e => {
+ console.error("Report Broken Site: getting a screenshot failed", e);
+ return Promise.resolve(undefined);
+ });
+ return {
+ antitracking: this.#getAntitrackingInfo(msg.target.browsingContext),
+ graphics: this.#getGraphicsInfo(),
+ locales: Services.locale.availableLocales,
+ screenshot,
+ security: getSecurityInfo(),
+ };
+ }
+ }
+ return null;
+ }
+}