Bug 1887594 - Fix saveTabToExistingCollectionFromMainMenuTest UI test r=aaronmt
[gecko.git] / toolkit / modules / Troubleshoot.sys.mjs
blobc716f1d94117063a02c34b49838bbdaa3d8bcb36
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs";
6 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
8 import { FeatureGate } from "resource://featuregates/FeatureGate.sys.mjs";
10 const lazy = {};
12 ChromeUtils.defineESModuleGetters(lazy, {
13   PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
14 });
16 // We use a list of prefs for display to make sure we only show prefs that
17 // are useful for support and won't compromise the user's privacy.  Note that
18 // entries are *prefixes*: for example, "accessibility." applies to all prefs
19 // under the "accessibility.*" branch.
20 const PREFS_FOR_DISPLAY = [
21   "accessibility.",
22   "apz.",
23   "browser.cache.",
24   "browser.contentblocking.category",
25   "browser.contentanalysis.",
26   "browser.display.",
27   "browser.download.always_ask_before_handling_new_types",
28   "browser.download.enable_spam_prevention",
29   "browser.download.folderList",
30   "browser.download.improvements_to_download_panel",
31   "browser.download.lastDir.savePerSite",
32   "browser.download.manager.addToRecentDocs",
33   "browser.download.manager.resumeOnWakeDelay",
34   "browser.download.open_pdf_attachments_inline",
35   "browser.download.preferred.",
36   "browser.download.skipConfirmLaunchExecutable",
37   "browser.download.start_downloads_in_tmp_dir",
38   "browser.download.useDownloadDir",
39   "browser.fixup.",
40   "browser.history_expire_",
41   "browser.link.open_newwindow",
42   "browser.places.",
43   "browser.privatebrowsing.",
44   "browser.search.context.loadInBackground",
45   "browser.search.log",
46   "browser.search.openintab",
47   "browser.search.param",
48   "browser.search.region",
49   "browser.search.searchEnginesURL",
50   "browser.search.suggest.enabled",
51   "browser.search.update",
52   "browser.sessionstore.",
53   "browser.startup.homepage",
54   "browser.startup.page",
55   "browser.tabs.",
56   "browser.toolbars.",
57   "browser.urlbar.",
58   "browser.zoom.",
59   "doh-rollout.",
60   "dom.",
61   "extensions.checkCompatibility",
62   "extensions.eventPages.enabled",
63   "extensions.formautofill.",
64   "extensions.lastAppVersion",
65   "extensions.manifestV3.enabled",
66   "extensions.quarantinedDomains.enabled",
67   "extensions.InstallTrigger.enabled",
68   "extensions.InstallTriggerImpl.enabled",
69   "fission.autostart",
70   "font.",
71   "general.autoScroll",
72   "general.useragent.",
73   "gfx.",
74   "html5.",
75   "identity.fxaccounts.enabled",
76   "idle.",
77   "image.",
78   "javascript.",
79   "keyword.",
80   "layers.",
81   "layout.css.dpi",
82   "layout.display-list.",
83   "layout.frame_rate",
84   "media.",
85   "mousewheel.",
86   "network.",
87   "permissions.default.image",
88   "places.",
89   "plugin.",
90   "plugins.",
91   "privacy.",
92   "security.",
93   "services.sync.declinedEngines",
94   "services.sync.lastPing",
95   "services.sync.lastSync",
96   "services.sync.numClients",
97   "services.sync.engine.",
98   "signon.",
99   "storage.vacuum.last.",
100   "svg.",
101   "toolkit.startup.recent_crashes",
102   "ui.osk.enabled",
103   "ui.osk.detect_physical_keyboard",
104   "ui.osk.require_tablet_mode",
105   "ui.osk.debug.keyboardDisplayReason",
106   "webgl.",
107   "widget.dmabuf",
108   "widget.use-xdg-desktop-portal",
109   "widget.use-xdg-desktop-portal.file-picker",
110   "widget.use-xdg-desktop-portal.mime-handler",
111   "widget.gtk.overlay-scrollbars.enabled",
112   "widget.wayland",
115 // The list of prefs we don't display, unlike the list of prefs for display,
116 // is a list of regular expressions.
117 const PREF_REGEXES_NOT_TO_DISPLAY = [
118   /^browser[.]fixup[.]domainwhitelist[.]/,
119   /^dom[.]push[.]userAgentID/,
120   /^media[.]webrtc[.]debug[.]aec_log_dir/,
121   /^media[.]webrtc[.]debug[.]log_file/,
122   /^print[.].*print_to_filename$/,
123   /^network[.]proxy[.]/,
126 // Table of getters for various preference types.
127 const PREFS_GETTERS = {};
129 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_STRING] = (prefs, name) =>
130   prefs.getStringPref(name);
131 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_INT] = (prefs, name) =>
132   prefs.getIntPref(name);
133 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_BOOL] = (prefs, name) =>
134   prefs.getBoolPref(name);
136 // List of unimportant locked prefs (won't be shown on the troubleshooting
137 // session)
138 const PREFS_UNIMPORTANT_LOCKED = [
139   "dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled",
140   "extensions.backgroundServiceWorkerEnabled.enabled",
141   "privacy.restrict3rdpartystorage.url_decorations",
144 function getPref(name) {
145   let type = Services.prefs.getPrefType(name);
146   if (!(type in PREFS_GETTERS)) {
147     throw new Error("Unknown preference type " + type + " for " + name);
148   }
149   return PREFS_GETTERS[type](Services.prefs, name);
152 // Return the preferences filtered by PREF_REGEXES_NOT_TO_DISPLAY and PREFS_FOR_DISPLAY
153 // and also by the custom 'filter'-ing function.
154 function getPrefList(filter, allowlist = PREFS_FOR_DISPLAY) {
155   return allowlist.reduce(function (prefs, branch) {
156     Services.prefs.getChildList(branch).forEach(function (name) {
157       if (
158         filter(name) &&
159         !PREF_REGEXES_NOT_TO_DISPLAY.some(re => re.test(name))
160       ) {
161         prefs[name] = getPref(name);
162       }
163     });
164     return prefs;
165   }, {});
168 export var Troubleshoot = {
169   /**
170    * Captures a snapshot of data that may help troubleshooters troubleshoot
171    * trouble.
172    *
173    * @returns {Promise}
174    *   A promise that is resolved with the snapshot data.
175    */
176   snapshot() {
177     return new Promise(resolve => {
178       let snapshot = {};
179       let numPending = Object.keys(dataProviders).length;
180       function providerDone(providerName, providerData) {
181         snapshot[providerName] = providerData;
182         if (--numPending == 0) {
183           // Ensure that done is always and truly called asynchronously.
184           Services.tm.dispatchToMainThread(() => resolve(snapshot));
185         }
186       }
187       for (let name in dataProviders) {
188         try {
189           dataProviders[name](providerDone.bind(null, name));
190         } catch (err) {
191           let msg = "Troubleshoot data provider failed: " + name + "\n" + err;
192           console.error(msg);
193           providerDone(name, msg);
194         }
195       }
196     });
197   },
199   kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days
202 // Each data provider is a name => function mapping.  When a snapshot is
203 // captured, each provider's function is called, and it's the function's job to
204 // generate the provider's data.  The function is passed a "done" callback, and
205 // when done, it must pass its data to the callback.  The resulting snapshot
206 // object will contain a name => data entry for each provider.
207 var dataProviders = {
208   application: async function application(done) {
209     let data = {
210       name: Services.appinfo.name,
211       osVersion:
212         Services.sysinfo.getProperty("name") +
213         " " +
214         Services.sysinfo.getProperty("version") +
215         " " +
216         Services.sysinfo.getProperty("build"),
217       version: AppConstants.MOZ_APP_VERSION_DISPLAY,
218       buildID: Services.appinfo.appBuildID,
219       distributionID: Services.prefs
220         .getDefaultBranch("")
221         .getCharPref("distribution.id", ""),
222       userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].getService(
223         Ci.nsIHttpProtocolHandler
224       ).userAgent,
225       safeMode: Services.appinfo.inSafeMode,
226       memorySizeBytes: Services.sysinfo.getProperty("memsize"),
227       diskAvailableBytes: Services.dirsvc.get("ProfD", Ci.nsIFile)
228         .diskSpaceAvailable,
229     };
231     if (Services.sysinfo.getProperty("name") == "Windows_NT") {
232       if ((await Services.sysinfo.processInfo).isWindowsSMode) {
233         data.osVersion += " S";
234       }
235     }
237     if (AppConstants.MOZ_UPDATER) {
238       data.updateChannel = ChromeUtils.importESModule(
239         "resource://gre/modules/UpdateUtils.sys.mjs"
240       ).UpdateUtils.UpdateChannel;
241     }
243     // eslint-disable-next-line mozilla/use-default-preference-values
244     try {
245       data.vendor = Services.prefs.getCharPref("app.support.vendor");
246     } catch (e) {}
247     try {
248       data.supportURL = Services.urlFormatter.formatURLPref(
249         "app.support.baseURL"
250       );
251     } catch (e) {}
253     data.osTheme = Services.sysinfo.getProperty("osThemeInfo");
255     try {
256       // MacOSX: Check for rosetta status, if it exists
257       data.rosetta = Services.sysinfo.getProperty("rosettaStatus");
258     } catch (e) {}
260     try {
261       // Windows - Get info about attached pointing devices
262       data.pointingDevices = Services.sysinfo
263         .getProperty("pointingDevices")
264         .split(",");
265     } catch (e) {}
267     data.numTotalWindows = 0;
268     data.numFissionWindows = 0;
269     data.numRemoteWindows = 0;
270     for (let { docShell } of Services.wm.getEnumerator("navigator:browser")) {
271       docShell.QueryInterface(Ci.nsILoadContext);
272       data.numTotalWindows++;
273       if (docShell.useRemoteSubframes) {
274         data.numFissionWindows++;
275       }
276       if (docShell.useRemoteTabs) {
277         data.numRemoteWindows++;
278       }
279     }
281     try {
282       data.launcherProcessState = Services.appinfo.launcherProcessState;
283     } catch (e) {}
285     data.fissionAutoStart = Services.appinfo.fissionAutostart;
286     data.fissionDecisionStatus = Services.appinfo.fissionDecisionStatusString;
288     data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart;
290     if (Services.policies) {
291       data.policiesStatus = Services.policies.status;
292     }
294     const keyLocationServiceGoogle = Services.urlFormatter
295       .formatURL("%GOOGLE_LOCATION_SERVICE_API_KEY%")
296       .trim();
297     data.keyLocationServiceGoogleFound =
298       keyLocationServiceGoogle != "no-google-location-service-api-key" &&
299       !!keyLocationServiceGoogle.length;
301     const keySafebrowsingGoogle = Services.urlFormatter
302       .formatURL("%GOOGLE_SAFEBROWSING_API_KEY%")
303       .trim();
304     data.keySafebrowsingGoogleFound =
305       keySafebrowsingGoogle != "no-google-safebrowsing-api-key" &&
306       !!keySafebrowsingGoogle.length;
308     const keyMozilla = Services.urlFormatter
309       .formatURL("%MOZILLA_API_KEY%")
310       .trim();
311     data.keyMozillaFound =
312       keyMozilla != "no-mozilla-api-key" && !!keyMozilla.length;
314     done(data);
315   },
317   addons: async function addons(done) {
318     let addons = await AddonManager.getAddonsByTypes([
319       "extension",
320       "locale",
321       "dictionary",
322       "sitepermission",
323       "theme",
324     ]);
325     addons = addons.filter(e => !e.isSystem);
326     addons.sort(function (a, b) {
327       if (a.isActive != b.isActive) {
328         return b.isActive ? 1 : -1;
329       }
331       if (a.type != b.type) {
332         return a.type.localeCompare(b.type);
333       }
335       // In some unfortunate cases add-on names can be null.
336       let aname = a.name || "";
337       let bname = b.name || "";
338       let lc = aname.localeCompare(bname);
339       if (lc != 0) {
340         return lc;
341       }
342       if (a.version != b.version) {
343         return a.version > b.version ? 1 : -1;
344       }
345       return 0;
346     });
347     let props = ["name", "type", "version", "isActive", "id"];
348     done(
349       addons.map(function (ext) {
350         return props.reduce(function (extData, prop) {
351           extData[prop] = ext[prop];
352           return extData;
353         }, {});
354       })
355     );
356   },
358   securitySoftware: function securitySoftware(done) {
359     let data = {};
361     const keys = [
362       "registeredAntiVirus",
363       "registeredAntiSpyware",
364       "registeredFirewall",
365     ];
366     for (let key of keys) {
367       let prop = "";
368       try {
369         prop = Services.sysinfo.getProperty(key);
370       } catch (e) {}
372       data[key] = prop;
373     }
375     done(data);
376   },
378   features: async function features(done) {
379     let features = await AddonManager.getAddonsByTypes(["extension"]);
380     features = features.filter(f => f.isSystem);
381     features.sort(function (a, b) {
382       // In some unfortunate cases addon names can be null.
383       let aname = a.name || null;
384       let bname = b.name || null;
385       let lc = aname.localeCompare(bname);
386       if (lc != 0) {
387         return lc;
388       }
389       if (a.version != b.version) {
390         return a.version > b.version ? 1 : -1;
391       }
392       return 0;
393     });
394     let props = ["name", "version", "id"];
395     done(
396       features.map(function (f) {
397         return props.reduce(function (fData, prop) {
398           fData[prop] = f[prop];
399           return fData;
400         }, {});
401       })
402     );
403   },
405   processes: async function processes(done) {
406     let remoteTypes = {};
407     const processInfo = await ChromeUtils.requestProcInfo();
408     for (let i = 0; i < processInfo.children.length; i++) {
409       let remoteType;
410       try {
411         remoteType = processInfo.children[i].type;
412         // Workaround for bug 1790070, since requestProcInfo refers to the preallocated content
413         // process as "preallocated", and the localization string mapping expects "prealloc".
414         remoteType = remoteType === "preallocated" ? "prealloc" : remoteType;
415       } catch (e) {}
417       // We will split Utility by actor name, so do not do it now
418       if (remoteType === "utility") {
419         continue;
420       }
422       // The parent process is also managed by the ppmm (because
423       // of non-remote tabs), but it doesn't have a remoteType.
424       if (!remoteType) {
425         continue;
426       }
428       if (remoteTypes[remoteType]) {
429         remoteTypes[remoteType]++;
430       } else {
431         remoteTypes[remoteType] = 1;
432       }
433     }
435     for (let i = 0; i < processInfo.children.length; i++) {
436       if (processInfo.children[i].type === "utility") {
437         for (let utilityWithActor of processInfo.children[i].utilityActors.map(
438           e => `utility_${e.actorName}`
439         )) {
440           if (remoteTypes[utilityWithActor]) {
441             remoteTypes[utilityWithActor]++;
442           } else {
443             remoteTypes[utilityWithActor] = 1;
444           }
445         }
446       }
447     }
449     try {
450       let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
451       if (winUtils.gpuProcessPid != -1) {
452         remoteTypes.gpu = 1;
453       }
454     } catch (e) {}
456     if (Services.io.socketProcessLaunched) {
457       remoteTypes.socket = 1;
458     }
460     let data = {
461       remoteTypes,
462       maxWebContentProcesses: Services.appinfo.maxWebProcessCount,
463     };
465     done(data);
466   },
468   async experimentalFeatures(done) {
469     if (AppConstants.platform == "android") {
470       done();
471       return;
472     }
473     let gates = await FeatureGate.all();
474     done(
475       gates.map(gate => {
476         return [
477           gate.title,
478           gate.preference,
479           Services.prefs.getBoolPref(gate.preference),
480         ];
481       })
482     );
483   },
485   async legacyUserStylesheets(done) {
486     if (AppConstants.platform == "android") {
487       done({ active: false, types: [] });
488       return;
489     }
491     let active = Services.prefs.getBoolPref(
492       "toolkit.legacyUserProfileCustomizations.stylesheets"
493     );
494     let types = [];
495     for (let name of ["userChrome.css", "userContent.css"]) {
496       let path = PathUtils.join(PathUtils.profileDir, "chrome", name);
497       if (await IOUtils.exists(path)) {
498         types.push(name);
499       }
500     }
501     done({ active, types });
502   },
504   async environmentVariables(done) {
505     let Subprocess;
506     try {
507       // Subprocess is not available in all builds
508       Subprocess = ChromeUtils.importESModule(
509         "resource://gre/modules/Subprocess.sys.mjs"
510       ).Subprocess;
511     } catch (ex) {
512       done({});
513       return;
514     }
516     let environment = Subprocess.getEnvironment();
517     let filteredEnvironment = {};
518     // Limit the environment variables to those that we
519     // know may affect Firefox to reduce leaking PII.
520     let filteredEnvironmentKeys = ["xre_", "moz_", "gdk", "display"];
521     for (let key of Object.keys(environment)) {
522       if (filteredEnvironmentKeys.some(k => key.toLowerCase().startsWith(k))) {
523         filteredEnvironment[key] = environment[key];
524       }
525     }
526     done(filteredEnvironment);
527   },
529   modifiedPreferences: function modifiedPreferences(done) {
530     done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
531   },
533   lockedPreferences: function lockedPreferences(done) {
534     done(
535       getPrefList(
536         name =>
537           !PREFS_UNIMPORTANT_LOCKED.includes(name) &&
538           Services.prefs.prefIsLocked(name)
539       )
540     );
541   },
543   places: async function places(done) {
544     const data = AppConstants.MOZ_PLACES
545       ? await lazy.PlacesDBUtils.getEntitiesStatsAndCounts()
546       : [];
547     done(data);
548   },
550   printingPreferences: function printingPreferences(done) {
551     let filter = name => Services.prefs.prefHasUserValue(name);
552     let prefs = getPrefList(filter, ["print."]);
554     // print_printer is special and is the only pref that is outside of the
555     // "print." branch... Maybe we should change it to print.printer or
556     // something...
557     if (filter("print_printer")) {
558       prefs.print_printer = getPref("print_printer");
559     }
561     done(prefs);
562   },
564   graphics: function graphics(done) {
565     function statusMsgForFeature(feature) {
566       // We return an object because in the try-newer-driver case we need to
567       // include the suggested version, which the consumer likely needs to plug
568       // into a format string from a localization file. Rather than returning
569       // a string in some cases and an object in others, return an object always.
570       let msg = { key: "" };
571       try {
572         var status = gfxInfo.getFeatureStatus(feature);
573       } catch (e) {}
574       switch (status) {
575         case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE:
576         case Ci.nsIGfxInfo.FEATURE_DISCOURAGED:
577           msg = { key: "blocked-gfx-card" };
578           break;
579         case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION:
580           msg = { key: "blocked-os-version" };
581           break;
582         case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION:
583           try {
584             var driverVersion =
585               gfxInfo.getFeatureSuggestedDriverVersion(feature);
586           } catch (e) {}
587           msg = driverVersion
588             ? { key: "try-newer-driver", args: { driverVersion } }
589             : { key: "blocked-driver" };
590           break;
591         case Ci.nsIGfxInfo.FEATURE_BLOCKED_MISMATCHED_VERSION:
592           msg = { key: "blocked-mismatched-version" };
593           break;
594       }
595       return msg;
596     }
598     let data = {};
600     try {
601       // nsIGfxInfo may not be implemented on some platforms.
602       var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
603     } catch (e) {}
605     data.desktopEnvironment = Services.appinfo.desktopEnvironment;
606     data.numTotalWindows = 0;
607     data.numAcceleratedWindows = 0;
609     let devicePixelRatios = [];
611     for (let win of Services.ww.getWindowEnumerator()) {
612       let winUtils = win.windowUtils;
613       try {
614         // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
615         if (
616           winUtils.layerManagerType == "None" ||
617           !winUtils.layerManagerRemote
618         ) {
619           continue;
620         }
621         devicePixelRatios.push(win.devicePixelRatio);
623         data.numTotalWindows++;
624         data.windowLayerManagerType = winUtils.layerManagerType;
625         data.windowLayerManagerRemote = winUtils.layerManagerRemote;
626       } catch (e) {
627         continue;
628       }
629       if (data.windowLayerManagerType != "Basic") {
630         data.numAcceleratedWindows++;
631       }
632     }
633     data.graphicsDevicePixelRatios = devicePixelRatios;
635     // If we had no OMTC windows, report back Basic Layers.
636     if (!data.windowLayerManagerType) {
637       data.windowLayerManagerType = "Basic";
638       data.windowLayerManagerRemote = false;
639     }
641     if (!data.numAcceleratedWindows && gfxInfo) {
642       let win = AppConstants.platform == "win";
643       let feature = win
644         ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS
645         : gfxInfo.FEATURE_OPENGL_LAYERS;
646       data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
647     }
649     if (gfxInfo) {
650       // keys are the names of attributes on nsIGfxInfo, values become the names
651       // of the corresponding properties in our data object.  A null value means
652       // no change.  This is needed so that the names of properties in the data
653       // object are the same as the names of keys in aboutSupport.properties.
654       let gfxInfoProps = {
655         adapterDescription: null,
656         adapterVendorID: null,
657         adapterDeviceID: null,
658         adapterSubsysID: null,
659         adapterRAM: null,
660         adapterDriver: "adapterDrivers",
661         adapterDriverVendor: "driverVendor",
662         adapterDriverVersion: "driverVersion",
663         adapterDriverDate: "driverDate",
665         adapterDescription2: null,
666         adapterVendorID2: null,
667         adapterDeviceID2: null,
668         adapterSubsysID2: null,
669         adapterRAM2: null,
670         adapterDriver2: "adapterDrivers2",
671         adapterDriverVendor2: "driverVendor2",
672         adapterDriverVersion2: "driverVersion2",
673         adapterDriverDate2: "driverDate2",
674         isGPU2Active: null,
676         D2DEnabled: "direct2DEnabled",
677         DWriteEnabled: "directWriteEnabled",
678         DWriteVersion: "directWriteVersion",
679         cleartypeParameters: "clearTypeParameters",
680         TargetFrameRate: "targetFrameRate",
681         windowProtocol: null,
682         fontVisibilityDeterminationStr: "supportFontDetermination",
683       };
685       for (let prop in gfxInfoProps) {
686         try {
687           data[gfxInfoProps[prop] || prop] = gfxInfo[prop];
688         } catch (e) {}
689       }
691       if ("direct2DEnabled" in data && !data.direct2DEnabled) {
692         data.direct2DEnabledMessage = statusMsgForFeature(
693           Ci.nsIGfxInfo.FEATURE_DIRECT2D
694         );
695       }
696     }
698     let doc = new DOMParser().parseFromString("<html/>", "text/html");
700     function GetWebGLInfo(data, keyPrefix, contextType) {
701       data[keyPrefix + "Renderer"] = "-";
702       data[keyPrefix + "Version"] = "-";
703       data[keyPrefix + "DriverExtensions"] = "-";
704       data[keyPrefix + "Extensions"] = "-";
705       data[keyPrefix + "WSIInfo"] = "-";
707       // //
709       let canvas = doc.createElement("canvas");
710       canvas.width = 1;
711       canvas.height = 1;
713       // //
715       let creationError = null;
717       canvas.addEventListener(
718         "webglcontextcreationerror",
720         function (e) {
721           creationError = e.statusMessage;
722         }
723       );
725       let gl = null;
726       try {
727         gl = canvas.getContext(contextType);
728       } catch (e) {
729         if (!creationError) {
730           creationError = e.toString();
731         }
732       }
733       if (!gl) {
734         data[keyPrefix + "Renderer"] =
735           creationError || "(no creation error info)";
736         return;
737       }
739       // //
741       data[keyPrefix + "Extensions"] = gl.getSupportedExtensions().join(" ");
743       // //
745       let ext = gl.getExtension("MOZ_debug");
746       // This extension is unconditionally available to chrome. No need to check.
747       let vendor = ext.getParameter(gl.VENDOR);
748       let renderer = ext.getParameter(gl.RENDERER);
750       data[keyPrefix + "Renderer"] = vendor + " -- " + renderer;
751       data[keyPrefix + "Version"] = ext.getParameter(gl.VERSION);
752       data[keyPrefix + "DriverExtensions"] = ext.getParameter(ext.EXTENSIONS);
753       data[keyPrefix + "WSIInfo"] = ext.getParameter(ext.WSI_INFO);
755       // //
757       // Eagerly free resources.
758       let loseExt = gl.getExtension("WEBGL_lose_context");
759       if (loseExt) {
760         loseExt.loseContext();
761       }
762     }
764     GetWebGLInfo(data, "webgl1", "webgl");
765     GetWebGLInfo(data, "webgl2", "webgl2");
767     if (gfxInfo) {
768       let infoInfo = gfxInfo.getInfo();
769       if (infoInfo) {
770         data.info = infoInfo;
771       }
773       let failureIndices = {};
775       let failures = gfxInfo.getFailures(failureIndices);
776       if (failures.length) {
777         data.failures = failures;
778         if (failureIndices.value.length == failures.length) {
779           data.indices = failureIndices.value;
780         }
781       }
783       data.featureLog = gfxInfo.getFeatureLog();
784       data.crashGuards = gfxInfo.getActiveCrashGuards();
785     }
787     function getNavigator() {
788       for (let win of Services.ww.getWindowEnumerator()) {
789         let winUtils = win.windowUtils;
790         try {
791           // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
792           if (
793             winUtils.layerManagerType == "None" ||
794             !winUtils.layerManagerRemote
795           ) {
796             continue;
797           }
798           const nav = win.navigator;
799           if (nav) {
800             return nav;
801           }
802         } catch (e) {
803           continue;
804         }
805       }
806       throw new Error("No window had window.navigator.");
807     }
809     const navigator = getNavigator();
811     async function GetWebgpuInfo(adapterOpts) {
812       const ret = {};
813       if (!navigator.gpu) {
814         ret["navigator.gpu"] = null;
815         return ret;
816       }
818       const requestAdapterkey = `navigator.gpu.requestAdapter(${JSON.stringify(
819         adapterOpts
820       )})`;
822       let adapter;
823       try {
824         adapter = await navigator.gpu.requestAdapter(adapterOpts);
825       } catch (e) {
826         // If WebGPU isn't supported or is blocked somehow, include
827         // that in the report. Anything else is an error which should
828         // have consequences (test failures, etc).
829         if (DOMException.isInstance(e) && e.name == "NotSupportedError") {
830           return { [requestAdapterkey]: { not_supported: e.message } };
831         }
832         throw e;
833       }
835       if (!adapter) {
836         ret[requestAdapterkey] = null;
837         return ret;
838       }
839       const desc = (ret[requestAdapterkey] = {});
841       desc.isFallbackAdapter = adapter.isFallbackAdapter;
843       const adapterInfo = await adapter.requestAdapterInfo();
844       // We can't directly enumerate properties of instances of `GPUAdapterInfo`s, so use the prototype instead.
845       const adapterInfoObj = {};
846       for (const k of Object.keys(Object.getPrototypeOf(adapterInfo)).sort()) {
847         adapterInfoObj[k] = adapterInfo[k];
848       }
849       desc[`requestAdapterInfo()`] = adapterInfoObj;
851       desc.features = Array.from(adapter.features).sort();
853       desc.limits = {};
854       const keys = Object.keys(Object.getPrototypeOf(adapter.limits)).sort(); // limits not directly enumerable?
855       for (const k of keys) {
856         desc.limits[k] = adapter.limits[k];
857       }
859       return ret;
860     }
862     // Webgpu info is going to need awaits.
863     (async () => {
864       data.webgpuDefaultAdapter = await GetWebgpuInfo({});
865       data.webgpuFallbackAdapter = await GetWebgpuInfo({
866         forceFallbackAdapter: true,
867       });
869       done(data);
870     })();
871   },
873   media: function media(done) {
874     function convertDevices(devices) {
875       if (!devices) {
876         return undefined;
877       }
878       let infos = [];
879       for (let i = 0; i < devices.length; ++i) {
880         let device = devices.queryElementAt(i, Ci.nsIAudioDeviceInfo);
881         infos.push({
882           name: device.name,
883           groupId: device.groupId,
884           vendor: device.vendor,
885           type: device.type,
886           state: device.state,
887           preferred: device.preferred,
888           supportedFormat: device.supportedFormat,
889           defaultFormat: device.defaultFormat,
890           maxChannels: device.maxChannels,
891           defaultRate: device.defaultRate,
892           maxRate: device.maxRate,
893           minRate: device.minRate,
894           maxLatency: device.maxLatency,
895           minLatency: device.minLatency,
896         });
897       }
898       return infos;
899     }
901     let data = {};
902     let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
903     data.currentAudioBackend = winUtils.currentAudioBackend;
904     data.currentMaxAudioChannels = winUtils.currentMaxAudioChannels;
905     data.currentPreferredSampleRate = winUtils.currentPreferredSampleRate;
906     data.audioOutputDevices = convertDevices(
907       winUtils
908         .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_OUTPUT)
909         .QueryInterface(Ci.nsIArray)
910     );
911     data.audioInputDevices = convertDevices(
912       winUtils
913         .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_INPUT)
914         .QueryInterface(Ci.nsIArray)
915     );
917     data.codecSupportInfo = "Unknown";
919     // We initialize gfxInfo here in the same way as in the media
920     // section -- should we break this out into a separate function?
921     try {
922       // nsIGfxInfo may not be implemented on some platforms.
923       var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
925       // Note: CodecSupportInfo is not populated until we have
926       // actually instantiated a PDM. We may want to add a button
927       // or some other means of allowing the user to manually
928       // instantiate a PDM to ensure that the data is available.
929       data.codecSupportInfo = gfxInfo.CodecSupportInfo;
930     } catch (e) {}
932     done(data);
933   },
935   accessibility: function accessibility(done) {
936     let data = {};
937     data.isActive = Services.appinfo.accessibilityEnabled;
938     // eslint-disable-next-line mozilla/use-default-preference-values
939     try {
940       data.forceDisabled = Services.prefs.getIntPref(
941         "accessibility.force_disabled"
942       );
943     } catch (e) {}
944     data.instantiator = Services.appinfo.accessibilityInstantiator;
945     done(data);
946   },
948   startupCache: function startupCache(done) {
949     const startupInfo = Cc["@mozilla.org/startupcacheinfo;1"].getService(
950       Ci.nsIStartupCacheInfo
951     );
952     done({
953       DiskCachePath: startupInfo.DiskCachePath,
954       IgnoreDiskCache: startupInfo.IgnoreDiskCache,
955       FoundDiskCacheOnInit: startupInfo.FoundDiskCacheOnInit,
956       WroteToDiskCache: startupInfo.WroteToDiskCache,
957     });
958   },
960   libraryVersions: function libraryVersions(done) {
961     let data = {};
962     let verInfo = Cc["@mozilla.org/security/nssversion;1"].getService(
963       Ci.nsINSSVersion
964     );
965     for (let prop in verInfo) {
966       let match = /^([^_]+)_((Min)?Version)$/.exec(prop);
967       if (match) {
968         let verProp = match[2][0].toLowerCase() + match[2].substr(1);
969         data[match[1]] = data[match[1]] || {};
970         data[match[1]][verProp] = verInfo[prop];
971       }
972     }
973     done(data);
974   },
976   userJS: function userJS(done) {
977     let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
978     userJSFile.append("user.js");
979     done({
980       exists: userJSFile.exists() && userJSFile.fileSize > 0,
981     });
982   },
984   intl: function intl(done) {
985     const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
986       Ci.mozIOSPreferences
987     );
988     done({
989       localeService: {
990         requested: Services.locale.requestedLocales,
991         available: Services.locale.availableLocales,
992         supported: Services.locale.appLocalesAsBCP47,
993         regionalPrefs: Services.locale.regionalPrefsLocales,
994         defaultLocale: Services.locale.defaultLocale,
995       },
996       osPrefs: {
997         systemLocales: osPrefs.systemLocales,
998         regionalPrefsLocales: osPrefs.regionalPrefsLocales,
999       },
1000     });
1001   },
1003   contentAnalysis: async function contentAnalysis(done) {
1004     const contentAnalysis = Cc["@mozilla.org/contentanalysis;1"].getService(
1005       Ci.nsIContentAnalysis
1006     );
1007     if (!contentAnalysis.isActive) {
1008       done({ active: false });
1009       return;
1010     }
1011     let info = await contentAnalysis.getDiagnosticInfo();
1012     done({
1013       active: true,
1014       connected: info.connectedToAgent,
1015       agentPath: info.agentPath,
1016       failedSignatureVerification: info.failedSignatureVerification,
1017       requestCount: info.requestCount,
1018     });
1019   },
1021   async normandy(done) {
1022     if (!AppConstants.MOZ_NORMANDY) {
1023       done();
1024       return;
1025     }
1027     const { PreferenceExperiments: NormandyPreferenceStudies } =
1028       ChromeUtils.importESModule(
1029         "resource://normandy/lib/PreferenceExperiments.sys.mjs"
1030       );
1031     const { AddonStudies: NormandyAddonStudies } = ChromeUtils.importESModule(
1032       "resource://normandy/lib/AddonStudies.sys.mjs"
1033     );
1034     const { PreferenceRollouts: NormandyPreferenceRollouts } =
1035       ChromeUtils.importESModule(
1036         "resource://normandy/lib/PreferenceRollouts.sys.mjs"
1037       );
1038     const { ExperimentManager } = ChromeUtils.importESModule(
1039       "resource://nimbus/lib/ExperimentManager.sys.mjs"
1040     );
1042     // Get Normandy data in parallel, and sort each group by slug.
1043     const [
1044       addonStudies,
1045       prefRollouts,
1046       prefStudies,
1047       nimbusExperiments,
1048       nimbusRollouts,
1049     ] = await Promise.all(
1050       [
1051         NormandyAddonStudies.getAllActive(),
1052         NormandyPreferenceRollouts.getAllActive(),
1053         NormandyPreferenceStudies.getAllActive(),
1054         ExperimentManager.store
1055           .ready()
1056           .then(() => ExperimentManager.store.getAllActiveExperiments()),
1057         ExperimentManager.store
1058           .ready()
1059           .then(() => ExperimentManager.store.getAllActiveRollouts()),
1060       ].map(promise =>
1061         promise
1062           .catch(error => {
1063             console.error(error);
1064             return [];
1065           })
1066           .then(items => items.sort((a, b) => a.slug.localeCompare(b.slug)))
1067       )
1068     );
1070     done({
1071       addonStudies,
1072       prefRollouts,
1073       prefStudies,
1074       nimbusExperiments,
1075       nimbusRollouts,
1076     });
1077   },
1080 if (AppConstants.MOZ_CRASHREPORTER) {
1081   dataProviders.crashes = function crashes(done) {
1082     const { CrashReports } = ChromeUtils.importESModule(
1083       "resource://gre/modules/CrashReports.sys.mjs"
1084     );
1085     let reports = CrashReports.getReports();
1086     let now = new Date();
1087     let reportsNew = reports.filter(
1088       report => now - report.date < Troubleshoot.kMaxCrashAge
1089     );
1090     let reportsSubmitted = reportsNew.filter(report => !report.pending);
1091     let reportsPendingCount = reportsNew.length - reportsSubmitted.length;
1092     let data = { submitted: reportsSubmitted, pending: reportsPendingCount };
1093     done(data);
1094   };
1097 if (AppConstants.MOZ_SANDBOX) {
1098   dataProviders.sandbox = function sandbox(done) {
1099     let data = {};
1100     if (AppConstants.unixstyle == "linux") {
1101       const keys = [
1102         "hasSeccompBPF",
1103         "hasSeccompTSync",
1104         "hasPrivilegedUserNamespaces",
1105         "hasUserNamespaces",
1106         "canSandboxContent",
1107         "canSandboxMedia",
1108       ];
1110       for (let key of keys) {
1111         if (Services.sysinfo.hasKey(key)) {
1112           data[key] = Services.sysinfo.getPropertyAsBool(key);
1113         }
1114       }
1116       let reporter = Cc["@mozilla.org/sandbox/syscall-reporter;1"].getService(
1117         Ci.mozISandboxReporter
1118       );
1119       const snapshot = reporter.snapshot();
1120       let syscalls = [];
1121       for (let index = snapshot.begin; index < snapshot.end; ++index) {
1122         let report = snapshot.getElement(index);
1123         let { msecAgo, pid, tid, procType, syscall } = report;
1124         let args = [];
1125         for (let i = 0; i < report.numArgs; ++i) {
1126           args.push(report.getArg(i));
1127         }
1128         syscalls.push({ index, msecAgo, pid, tid, procType, syscall, args });
1129       }
1130       data.syscallLog = syscalls;
1131     }
1133     if (AppConstants.MOZ_SANDBOX) {
1134       let sandboxSettings = Cc[
1135         "@mozilla.org/sandbox/sandbox-settings;1"
1136       ].getService(Ci.mozISandboxSettings);
1137       data.contentSandboxLevel = Services.prefs.getIntPref(
1138         "security.sandbox.content.level"
1139       );
1140       data.effectiveContentSandboxLevel =
1141         sandboxSettings.effectiveContentSandboxLevel;
1143       if (AppConstants.platform == "win") {
1144         data.contentWin32kLockdownState =
1145           sandboxSettings.contentWin32kLockdownStateString;
1147         data.supportSandboxGpuLevel = Services.prefs.getIntPref(
1148           "security.sandbox.gpu.level"
1149         );
1150       }
1151     }
1153     done(data);
1154   };
1157 if (AppConstants.ENABLE_WEBDRIVER) {
1158   dataProviders.remoteAgent = function remoteAgent(done) {
1159     const { RemoteAgent } = ChromeUtils.importESModule(
1160       "chrome://remote/content/components/RemoteAgent.sys.mjs"
1161     );
1162     const { running, scheme, host, port } = RemoteAgent;
1163     let url = "";
1164     if (running) {
1165       url = `${scheme}://${host}:${port}/`;
1166     }
1167     done({ running, url });
1168   };