1 /* vim: set ts=2 sw=2 et tw=80: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 import { Troubleshoot } from "resource://gre/modules/Troubleshoot.sys.mjs";
8 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
10 export class ReportBrokenSiteParent extends JSWindowActorParent {
11 #getAntitrackingBlockList() {
12 // If content-track-digest256 is in the tracking table,
13 // the user has enabled the strict list.
14 const trackingTable = Services.prefs.getCharPref(
15 "urlclassifier.trackingTable"
17 return trackingTable.includes("content") ? "strict" : "basic";
20 #getAntitrackingInfo(browsingContext) {
21 // Ask BounceTrackingProtection whether it has recently purged state for the
22 // site in the current top level context.
23 let btpHasPurgedSite = false;
25 Services.prefs.getIntPref("privacy.bounceTrackingProtection.mode") !=
26 Ci.nsIBounceTrackingProtection.MODE_DISABLED
28 let bounceTrackingProtection = Cc[
29 "@mozilla.org/bounce-tracking-protection;1"
30 ].getService(Ci.nsIBounceTrackingProtection);
32 let { currentWindowGlobal } = browsingContext;
33 if (currentWindowGlobal) {
34 let { documentPrincipal } = currentWindowGlobal;
35 let { baseDomain } = documentPrincipal;
37 bounceTrackingProtection.hasRecentlyPurgedSite(baseDomain);
42 blockList: this.#getAntitrackingBlockList(),
43 isPrivateBrowsing: browsingContext.usePrivateBrowsing,
44 hasTrackingContentBlocked: !!(
45 browsingContext.currentWindowGlobal.contentBlockingEvents &
46 Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT
48 hasMixedActiveContentBlocked: !!(
49 browsingContext.secureBrowserUI.state &
50 Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
52 hasMixedDisplayContentBlocked: !!(
53 browsingContext.secureBrowserUI.state &
54 Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT
68 const clean = rawObj => {
69 const obj = JSON.parse(JSON.stringify(rawObj));
70 if (!Object.keys(obj).length) {
76 const cleanDevice = (vendorID, deviceID, subsysID) => {
77 return clean({ vendorID, deviceID, subsysID });
80 const d1 = cleanDevice(
81 get("adapterVendorID"),
82 get("adapterDeviceID"),
83 get("adapterSubsysID")
85 const d2 = cleanDevice(
86 get("adapterVendorID2"),
87 get("adapterDeviceID2"),
88 get("adapterSubsysID2")
90 const devices = (get("isGPU2Active") ? [d2, d1] : [d1, d2]).filter(
95 direct2DEnabled: get("direct2DEnabled"),
96 directWriteEnabled: get("directWriteEnabled"),
97 directWriteVersion: get("directWriteVersion"),
98 hasTouchScreen: info.ApzTouchInput == 1,
99 clearTypeParameters: get("clearTypeParameters"),
100 targetFrameRate: get("targetFrameRate"),
105 #parseCodecSupportInfo(codecSupportInfo) {
106 if (!codecSupportInfo) {
111 for (const item of codecSupportInfo.split("\n")) {
112 const [codec, ...types] = item.split(" ");
113 if (!codecs[codec]) {
114 codecs[codec] = { hardware: false, software: false };
116 codecs[codec].software ||= types.includes("SW");
117 codecs[codec].hardware ||= types.includes("HW");
122 #parseFeatureLog(featureLog = {}) {
123 const { features } = featureLog;
128 const parsedFeatures = {};
129 for (let { name, log, status } of features) {
130 for (const item of log.reverse()) {
131 if (!item.failureId || item.status != status) {
134 status = `${status} (${item.message || item.failureId})`;
136 parsedFeatures[name] = status;
138 return parsedFeatures;
141 #getGraphicsInfo(troubleshoot) {
142 const { graphics, media } = troubleshoot;
143 const { featureLog } = graphics;
144 const data = this.#parseGfxInfo(graphics);
147 renderer: graphics.webgl1Renderer,
148 version: graphics.webgl1Version,
151 renderer: graphics.webgl2Renderer,
152 version: graphics.webgl2Version,
154 ].filter(({ version }) => version && version != "-");
156 data.codecSupport = this.#parseCodecSupportInfo(media.codecSupportInfo);
157 data.features = this.#parseFeatureLog(featureLog);
159 const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
160 data.monitors = gfxInfo.getMonitors();
165 #getAppInfo(troubleshootingInfo) {
166 const { application } = troubleshootingInfo;
168 applicationName: application.name,
169 buildId: application.buildID,
170 defaultUserAgent: application.userAgent,
171 updateChannel: application.updateChannel,
172 version: application.version,
176 #getSysinfoProperty(propertyName, defaultValue) {
178 return Services.sysinfo.getProperty(propertyName);
186 "layers.acceleration.force-enabled",
187 "gfx.webrender.software",
188 "browser.opaqueResponseBlocking",
189 "extensions.InstallTrigger.enabled",
190 "privacy.resistFingerprinting",
191 "privacy.globalprivacycontrol.enabled",
192 "network.cookie.cookieBehavior.optInPartitioning",
193 "network.cookie.cookieBehavior.optInPartitioning.pbmode",
195 prefs[name] = Services.prefs.getBoolPref(name, undefined);
197 const cookieBehavior = "network.cookie.cookieBehavior";
198 prefs[cookieBehavior] = Services.prefs.getIntPref(cookieBehavior, -1);
202 async #getPlatformInfo(troubleshootingInfo) {
203 const { application } = troubleshootingInfo;
204 const { memorySizeBytes, fissionAutoStart } = application;
206 let memoryMB = memorySizeBytes;
208 memoryMB = Math.round(memoryMB / 1024 / 1024);
212 fissionEnabled: fissionAutoStart,
214 osArchitecture: this.#getSysinfoProperty("arch", null),
215 osName: this.#getSysinfoProperty("name", null),
216 osVersion: this.#getSysinfoProperty("version", null),
217 name: AppConstants.platform,
219 if (info.os === "android") {
220 info.device = this.#getSysinfoProperty("device", null);
221 info.isTablet = this.#getSysinfoProperty("tablet", false);
224 info.osName == "Windows_NT" &&
225 (await Services.sysinfo.processInfo).isWindowsSMode
227 info.osVersion += " S";
232 #getSecurityInfo(troubleshootingInfo) {
234 for (const [k, v] of Object.entries(troubleshootingInfo.securitySoftware)) {
235 result[k.replace("registered", "").toLowerCase()] = v
240 // Right now, security data is only available for Windows builds, and
241 // we might as well not return anything at all if no data is available.
242 if (!Object.values(result).filter(e => e).length) {
249 async #getBrowserInfo() {
250 const troubleshootingInfo = await Troubleshoot.snapshot();
252 app: this.#getAppInfo(troubleshootingInfo),
253 graphics: this.#getGraphicsInfo(troubleshootingInfo),
254 locales: troubleshootingInfo.intl.localeService.available,
255 prefs: this.#getPrefs(),
256 platform: await this.#getPlatformInfo(troubleshootingInfo),
257 security: this.#getSecurityInfo(troubleshootingInfo),
261 async #getScreenshot(browsingContext, format, quality) {
262 const zoom = browsingContext.fullZoom;
263 const scale = browsingContext.topChromeWindow?.devicePixelRatio || 1;
264 const wgp = browsingContext.currentWindowGlobal;
266 const image = await wgp.drawSnapshot(
270 undefined // resetScrollPosition
273 const canvas = new OffscreenCanvas(image.width, image.height);
275 const ctx = canvas.getContext("bitmaprenderer", { alpha: false });
276 ctx.transferFromImageBitmap(image);
278 const blob = await canvas.convertToBlob({
279 type: `image/${format}`,
280 quality: quality / 100,
283 const dataURL = await new Promise((resolve, reject) => {
284 let reader = new FileReader();
285 reader.onload = () => resolve(reader.result);
286 reader.onerror = () => reject(reader.error);
287 reader.readAsDataURL(blob);
293 async receiveMessage(msg) {
295 case "GetWebcompatInfoFromParentProcess": {
296 const { format, quality } = msg.data;
297 const screenshot = await this.#getScreenshot(
298 msg.target.browsingContext,
302 console.error("Report Broken Site: getting a screenshot failed", e);
303 return Promise.resolve(undefined);
307 antitracking: this.#getAntitrackingInfo(msg.target.browsingContext),
308 browser: await this.#getBrowserInfo(),