Bug 1933479 - Add tab close button on hover to vertical tabs when sidebar is collapse...
[gecko.git] / toolkit / components / reportbrokensite / ReportBrokenSiteParent.sys.mjs
blob01533797a8920fa00bf5f8ac041b41c737e96251
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"
16     );
17     return trackingTable.includes("content") ? "strict" : "basic";
18   }
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;
24     if (
25       Services.prefs.getIntPref("privacy.bounceTrackingProtection.mode") !=
26       Ci.nsIBounceTrackingProtection.MODE_DISABLED
27     ) {
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;
36         btpHasPurgedSite =
37           bounceTrackingProtection.hasRecentlyPurgedSite(baseDomain);
38       }
39     }
41     return {
42       blockList: this.#getAntitrackingBlockList(),
43       isPrivateBrowsing: browsingContext.usePrivateBrowsing,
44       hasTrackingContentBlocked: !!(
45         browsingContext.currentWindowGlobal.contentBlockingEvents &
46         Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT
47       ),
48       hasMixedActiveContentBlocked: !!(
49         browsingContext.secureBrowserUI.state &
50         Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
51       ),
52       hasMixedDisplayContentBlocked: !!(
53         browsingContext.secureBrowserUI.state &
54         Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT
55       ),
56       btpHasPurgedSite,
57     };
58   }
60   #parseGfxInfo(info) {
61     const get = name => {
62       try {
63         return info[name];
64       } catch (e) {}
65       return undefined;
66     };
68     const clean = rawObj => {
69       const obj = JSON.parse(JSON.stringify(rawObj));
70       if (!Object.keys(obj).length) {
71         return undefined;
72       }
73       return obj;
74     };
76     const cleanDevice = (vendorID, deviceID, subsysID) => {
77       return clean({ vendorID, deviceID, subsysID });
78     };
80     const d1 = cleanDevice(
81       get("adapterVendorID"),
82       get("adapterDeviceID"),
83       get("adapterSubsysID")
84     );
85     const d2 = cleanDevice(
86       get("adapterVendorID2"),
87       get("adapterDeviceID2"),
88       get("adapterSubsysID2")
89     );
90     const devices = (get("isGPU2Active") ? [d2, d1] : [d1, d2]).filter(
91       v => v !== undefined
92     );
94     return clean({
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"),
101       devices,
102     });
103   }
105   #parseCodecSupportInfo(codecSupportInfo) {
106     if (!codecSupportInfo) {
107       return undefined;
108     }
110     const codecs = {};
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 };
115       }
116       codecs[codec].software ||= types.includes("SW");
117       codecs[codec].hardware ||= types.includes("HW");
118     }
119     return codecs;
120   }
122   #parseFeatureLog(featureLog = {}) {
123     const { features } = featureLog;
124     if (!features) {
125       return undefined;
126     }
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) {
132           continue;
133         }
134         status = `${status} (${item.message || item.failureId})`;
135       }
136       parsedFeatures[name] = status;
137     }
138     return parsedFeatures;
139   }
141   #getGraphicsInfo(troubleshoot) {
142     const { graphics, media } = troubleshoot;
143     const { featureLog } = graphics;
144     const data = this.#parseGfxInfo(graphics);
145     data.drivers = [
146       {
147         renderer: graphics.webgl1Renderer,
148         version: graphics.webgl1Version,
149       },
150       {
151         renderer: graphics.webgl2Renderer,
152         version: graphics.webgl2Version,
153       },
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();
162     return data;
163   }
165   #getAppInfo(troubleshootingInfo) {
166     const { application } = troubleshootingInfo;
167     return {
168       applicationName: application.name,
169       buildId: application.buildID,
170       defaultUserAgent: application.userAgent,
171       updateChannel: application.updateChannel,
172       version: application.version,
173     };
174   }
176   #getSysinfoProperty(propertyName, defaultValue) {
177     try {
178       return Services.sysinfo.getProperty(propertyName);
179     } catch (e) {}
180     return defaultValue;
181   }
183   #getPrefs() {
184     const prefs = {};
185     for (const name of [
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",
194     ]) {
195       prefs[name] = Services.prefs.getBoolPref(name, undefined);
196     }
197     const cookieBehavior = "network.cookie.cookieBehavior";
198     prefs[cookieBehavior] = Services.prefs.getIntPref(cookieBehavior, -1);
199     return prefs;
200   }
202   async #getPlatformInfo(troubleshootingInfo) {
203     const { application } = troubleshootingInfo;
204     const { memorySizeBytes, fissionAutoStart } = application;
206     let memoryMB = memorySizeBytes;
207     if (memoryMB) {
208       memoryMB = Math.round(memoryMB / 1024 / 1024);
209     }
211     const info = {
212       fissionEnabled: fissionAutoStart,
213       memoryMB,
214       osArchitecture: this.#getSysinfoProperty("arch", null),
215       osName: this.#getSysinfoProperty("name", null),
216       osVersion: this.#getSysinfoProperty("version", null),
217       name: AppConstants.platform,
218     };
219     if (info.os === "android") {
220       info.device = this.#getSysinfoProperty("device", null);
221       info.isTablet = this.#getSysinfoProperty("tablet", false);
222     }
223     if (
224       info.osName == "Windows_NT" &&
225       (await Services.sysinfo.processInfo).isWindowsSMode
226     ) {
227       info.osVersion += " S";
228     }
229     return info;
230   }
232   #getSecurityInfo(troubleshootingInfo) {
233     const result = {};
234     for (const [k, v] of Object.entries(troubleshootingInfo.securitySoftware)) {
235       result[k.replace("registered", "").toLowerCase()] = v
236         ? v.split(";")
237         : null;
238     }
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) {
243       return undefined;
244     }
246     return result;
247   }
249   async #getBrowserInfo() {
250     const troubleshootingInfo = await Troubleshoot.snapshot();
251     return {
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),
258     };
259   }
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(
267       undefined, // rect
268       scale * zoom,
269       "white",
270       undefined // resetScrollPosition
271     );
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,
281     });
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);
288     });
290     return dataURL;
291   }
293   async receiveMessage(msg) {
294     switch (msg.name) {
295       case "GetWebcompatInfoFromParentProcess": {
296         const { format, quality } = msg.data;
297         const screenshot = await this.#getScreenshot(
298           msg.target.browsingContext,
299           format,
300           quality
301         ).catch(e => {
302           console.error("Report Broken Site: getting a screenshot failed", e);
303           return Promise.resolve(undefined);
304         });
306         return {
307           antitracking: this.#getAntitrackingInfo(msg.target.browsingContext),
308           browser: await this.#getBrowserInfo(),
309           screenshot,
310         };
311       }
312     }
313     return null;
314   }