diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs | 292 |
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; + } +} |