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 var EXPORTED_SYMBOLS = ["Troubleshoot"];
7 const { AddonManager } = ChromeUtils.import(
8 "resource://gre/modules/AddonManager.jsm"
10 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
11 const { AppConstants } = ChromeUtils.import(
12 "resource://gre/modules/AppConstants.jsm"
14 const { E10SUtils } = ChromeUtils.import(
15 "resource://gre/modules/E10SUtils.jsm"
17 const { XPCOMUtils } = ChromeUtils.import(
18 "resource://gre/modules/XPCOMUtils.jsm"
20 XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser"]);
22 // We use a preferences whitelist to make sure we only show preferences that
23 // are useful for support and won't compromise the user's privacy. Note that
24 // entries are *prefixes*: for example, "accessibility." applies to all prefs
25 // under the "accessibility.*" branch.
26 const PREFS_WHITELIST = [
30 "browser.contentblocking.category",
32 "browser.download.folderList",
33 "browser.download.hide_plugins_without_extensions",
34 "browser.download.lastDir.savePerSite",
35 "browser.download.manager.addToRecentDocs",
36 "browser.download.manager.resumeOnWakeDelay",
37 "browser.download.preferred.",
38 "browser.download.skipConfirmLaunchExecutable",
39 "browser.download.useDownloadDir",
41 "browser.history_expire_",
42 "browser.link.open_newwindow",
44 "browser.privatebrowsing.",
45 "browser.search.context.loadInBackground",
47 "browser.search.openintab",
48 "browser.search.param",
49 "browser.search.searchEnginesURL",
50 "browser.search.suggest.enabled",
51 "browser.search.update",
52 "browser.search.useDBForOrder",
53 "browser.sessionstore.",
54 "browser.startup.homepage",
55 "browser.startup.page",
60 "extensions.checkCompatibility",
61 "extensions.formautofill.",
62 "extensions.lastAppVersion",
69 "identity.fxaccounts.enabled",
76 "layout.display-list.",
80 "permissions.default.image",
88 "services.sync.declinedEngines",
89 "services.sync.lastPing",
90 "services.sync.lastSync",
91 "services.sync.numClients",
92 "services.sync.engine.",
94 "storage.vacuum.last.",
96 "toolkit.startup.recent_crashes",
98 "ui.osk.detect_physical_keyboard",
99 "ui.osk.require_tablet_mode",
100 "ui.osk.debug.keyboardDisplayReason",
104 // The blacklist, unlike the whitelist, is a list of regular expressions.
105 const PREFS_BLACKLIST = [
106 /^browser[.]fixup[.]domainwhitelist[.]/,
107 /^media[.]webrtc[.]debug[.]aec_log_dir/,
108 /^media[.]webrtc[.]debug[.]log_file/,
109 /^network[.]proxy[.]/,
110 /[.]print_to_filename$/,
111 /^print[.]macosx[.]pagesetup/,
115 // Table of getters for various preference types.
116 const PREFS_GETTERS = {};
118 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_STRING] = (prefs, name) =>
119 prefs.getStringPref(name);
120 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_INT] = (prefs, name) =>
121 prefs.getIntPref(name);
122 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_BOOL] = (prefs, name) =>
123 prefs.getBoolPref(name);
125 // List of unimportant locked prefs (won't be shown on the troubleshooting
127 const PREFS_UNIMPORTANT_LOCKED = [
128 "dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled",
129 "privacy.restrict3rdpartystorage.url_decorations",
132 // Return the preferences filtered by PREFS_BLACKLIST and PREFS_WHITELIST lists
133 // and also by the custom 'filter'-ing function.
134 function getPrefList(filter) {
135 filter = filter || (name => true);
136 function getPref(name) {
137 let type = Services.prefs.getPrefType(name);
138 if (!(type in PREFS_GETTERS)) {
139 throw new Error("Unknown preference type " + type + " for " + name);
141 return PREFS_GETTERS[type](Services.prefs, name);
144 return PREFS_WHITELIST.reduce(function(prefs, branch) {
145 Services.prefs.getChildList(branch).forEach(function(name) {
146 if (filter(name) && !PREFS_BLACKLIST.some(re => re.test(name))) {
147 prefs[name] = getPref(name);
156 * Captures a snapshot of data that may help troubleshooters troubleshoot
159 * @param done A function that will be asynchronously called when the
160 * snapshot completes. It will be passed the snapshot object.
162 snapshot: function snapshot(done) {
164 let numPending = Object.keys(dataProviders).length;
165 function providerDone(providerName, providerData) {
166 snapshot[providerName] = providerData;
167 if (--numPending == 0) {
168 // Ensure that done is always and truly called asynchronously.
169 Services.tm.dispatchToMainThread(done.bind(null, snapshot));
172 for (let name in dataProviders) {
174 dataProviders[name](providerDone.bind(null, name));
176 let msg = "Troubleshoot data provider failed: " + name + "\n" + err;
178 providerDone(name, msg);
183 kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days
186 // Each data provider is a name => function mapping. When a snapshot is
187 // captured, each provider's function is called, and it's the function's job to
188 // generate the provider's data. The function is passed a "done" callback, and
189 // when done, it must pass its data to the callback. The resulting snapshot
190 // object will contain a name => data entry for each provider.
191 var dataProviders = {
192 application: function application(done) {
194 name: Services.appinfo.name,
196 Services.sysinfo.getProperty("name") +
198 Services.sysinfo.getProperty("version"),
199 version: AppConstants.MOZ_APP_VERSION_DISPLAY,
200 buildID: Services.appinfo.appBuildID,
201 userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].getService(
202 Ci.nsIHttpProtocolHandler
204 safeMode: Services.appinfo.inSafeMode,
207 if (AppConstants.MOZ_UPDATER) {
208 data.updateChannel = ChromeUtils.import(
209 "resource://gre/modules/UpdateUtils.jsm",
211 ).UpdateUtils.UpdateChannel;
214 // eslint-disable-next-line mozilla/use-default-preference-values
216 data.vendor = Services.prefs.getCharPref("app.support.vendor");
219 data.supportURL = Services.urlFormatter.formatURLPref(
220 "app.support.baseURL"
224 data.numTotalWindows = 0;
225 data.numRemoteWindows = 0;
226 for (let { docShell } of Services.wm.getEnumerator("navigator:browser")) {
227 data.numTotalWindows++;
228 let remote = docShell.QueryInterface(Ci.nsILoadContext).useRemoteTabs;
230 data.numRemoteWindows++;
235 data.launcherProcessState = Services.appinfo.launcherProcessState;
238 data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart;
241 let e10sStatus = Cc["@mozilla.org/supports-PRUint64;1"].createInstance(
242 Ci.nsISupportsPRUint64
244 let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
245 appinfo.observe(e10sStatus, "getE10SBlocked", "");
246 data.autoStartStatus = e10sStatus.data;
248 data.autoStartStatus = -1;
251 if (Services.policies) {
252 data.policiesStatus = Services.policies.status;
255 const keyLocationServiceGoogle = Services.urlFormatter
256 .formatURL("%GOOGLE_LOCATION_SERVICE_API_KEY%")
258 data.keyLocationServiceGoogleFound =
259 keyLocationServiceGoogle != "no-google-location-service-api-key" &&
260 !!keyLocationServiceGoogle.length;
262 const keySafebrowsingGoogle = Services.urlFormatter
263 .formatURL("%GOOGLE_SAFEBROWSING_API_KEY%")
265 data.keySafebrowsingGoogleFound =
266 keySafebrowsingGoogle != "no-google-safebrowsing-api-key" &&
267 !!keySafebrowsingGoogle.length;
269 const keyMozilla = Services.urlFormatter
270 .formatURL("%MOZILLA_API_KEY%")
272 data.keyMozillaFound =
273 keyMozilla != "no-mozilla-api-key" && !!keyMozilla.length;
278 extensions: async function extensions(done) {
279 let extensions = await AddonManager.getAddonsByTypes(["extension"]);
280 extensions = extensions.filter(e => !e.isSystem);
281 extensions.sort(function(a, b) {
282 if (a.isActive != b.isActive) {
283 return b.isActive ? 1 : -1;
286 // In some unfortunate cases addon names can be null.
287 let aname = a.name || "";
288 let bname = b.name || "";
289 let lc = aname.localeCompare(bname);
293 if (a.version != b.version) {
294 return a.version > b.version ? 1 : -1;
298 let props = ["name", "version", "isActive", "id"];
300 extensions.map(function(ext) {
301 return props.reduce(function(extData, prop) {
302 extData[prop] = ext[prop];
309 securitySoftware: function securitySoftware(done) {
312 let sysInfo = Cc["@mozilla.org/system-info;1"].getService(
317 "registeredAntiVirus",
318 "registeredAntiSpyware",
319 "registeredFirewall",
321 for (let key of keys) {
324 prop = sysInfo.getProperty(key);
333 features: async function features(done) {
334 let features = await AddonManager.getAddonsByTypes(["extension"]);
335 features = features.filter(f => f.isSystem);
336 features.sort(function(a, b) {
337 // In some unfortunate cases addon names can be null.
338 let aname = a.name || null;
339 let bname = b.name || null;
340 let lc = aname.localeCompare(bname);
344 if (a.version != b.version) {
345 return a.version > b.version ? 1 : -1;
349 let props = ["name", "version", "id"];
351 features.map(function(f) {
352 return props.reduce(function(fData, prop) {
353 fData[prop] = f[prop];
360 processes: function processes(done) {
361 let remoteTypes = {};
363 for (let i = 0; i < Services.ppmm.childCount; i++) {
366 remoteType = Services.ppmm.getChildAt(i).remoteType;
369 // The parent process is also managed by the ppmm (because
370 // of non-remote tabs), but it doesn't have a remoteType.
375 remoteType = E10SUtils.remoteTypePrefix(remoteType);
377 if (remoteTypes[remoteType]) {
378 remoteTypes[remoteType]++;
380 remoteTypes[remoteType] = 1;
385 let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
386 if (winUtils.gpuProcessPid != -1) {
393 maxWebContentProcesses: Services.appinfo.maxWebProcessCount,
399 modifiedPreferences: function modifiedPreferences(done) {
400 done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
403 lockedPreferences: function lockedPreferences(done) {
407 !PREFS_UNIMPORTANT_LOCKED.includes(name) &&
408 Services.prefs.prefIsLocked(name)
413 graphics: function graphics(done) {
414 function statusMsgForFeature(feature) {
415 // We return an object because in the try-newer-driver case we need to
416 // include the suggested version, which the consumer likely needs to plug
417 // into a format string from a localization file. Rather than returning
418 // a string in some cases and an object in others, return an object always.
419 let msg = { key: "" };
421 var status = gfxInfo.getFeatureStatus(feature);
424 case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE:
425 case Ci.nsIGfxInfo.FEATURE_DISCOURAGED:
426 msg = { key: "blocked-gfx-card" };
428 case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION:
429 msg = { key: "blocked-os-version" };
431 case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION:
433 var driverVersion = gfxInfo.getFeatureSuggestedDriverVersion(
438 ? { key: "try-newer-driver", args: { driverVersion } }
439 : { key: "blocked-driver" };
441 case Ci.nsIGfxInfo.FEATURE_BLOCKED_MISMATCHED_VERSION:
442 msg = { key: "blocked-mismatched-version" };
451 // nsIGfxInfo may not be implemented on some platforms.
452 var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
456 // done will be called upon all pending promises being resolved.
457 // add your pending promise to promises when adding new ones.
458 function completed() {
459 Promise.all(promises).then(() => done(data));
462 data.numTotalWindows = 0;
463 data.numAcceleratedWindows = 0;
464 for (let win of Services.ww.getWindowEnumerator()) {
465 let winUtils = win.windowUtils;
467 // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
469 winUtils.layerManagerType == "None" ||
470 !winUtils.layerManagerRemote
474 data.numTotalWindows++;
475 data.windowLayerManagerType = winUtils.layerManagerType;
476 data.windowLayerManagerRemote = winUtils.layerManagerRemote;
477 data.windowUsingAdvancedLayers = winUtils.usingAdvancedLayers;
481 if (data.windowLayerManagerType != "Basic") {
482 data.numAcceleratedWindows++;
486 // If we had no OMTC windows, report back Basic Layers.
487 if (!data.windowLayerManagerType) {
488 data.windowLayerManagerType = "Basic";
489 data.windowLayerManagerRemote = false;
492 if (!data.numAcceleratedWindows && gfxInfo) {
493 let win = AppConstants.platform == "win";
495 ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS
496 : gfxInfo.FEATURE_OPENGL_LAYERS;
497 data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
505 // keys are the names of attributes on nsIGfxInfo, values become the names
506 // of the corresponding properties in our data object. A null value means
507 // no change. This is needed so that the names of properties in the data
508 // object are the same as the names of keys in aboutSupport.properties.
510 adapterDescription: null,
511 adapterVendorID: null,
512 adapterDeviceID: null,
513 adapterSubsysID: null,
515 adapterDriver: "adapterDrivers",
516 adapterDriverVendor: "driverVendor",
517 adapterDriverVersion: "driverVersion",
518 adapterDriverDate: "driverDate",
520 adapterDescription2: null,
521 adapterVendorID2: null,
522 adapterDeviceID2: null,
523 adapterSubsysID2: null,
525 adapterDriver2: "adapterDrivers2",
526 adapterDriverVendor2: "driverVendor2",
527 adapterDriverVersion2: "driverVersion2",
528 adapterDriverDate2: "driverDate2",
531 D2DEnabled: "direct2DEnabled",
532 DWriteEnabled: "directWriteEnabled",
533 DWriteVersion: "directWriteVersion",
534 cleartypeParameters: "clearTypeParameters",
535 UsesTiling: "usesTiling",
536 ContentUsesTiling: "contentUsesTiling",
537 OffMainThreadPaintEnabled: "offMainThreadPaintEnabled",
538 OffMainThreadPaintWorkerCount: "offMainThreadPaintWorkerCount",
539 TargetFrameRate: "targetFrameRate",
540 windowProtocol: null,
541 desktopEnvironment: null,
544 for (let prop in gfxInfoProps) {
546 data[gfxInfoProps[prop] || prop] = gfxInfo[prop];
550 if ("direct2DEnabled" in data && !data.direct2DEnabled) {
551 data.direct2DEnabledMessage = statusMsgForFeature(
552 Ci.nsIGfxInfo.FEATURE_DIRECT2D
556 let doc = new DOMParser().parseFromString("<html/>", "text/html");
558 function GetWebGLInfo(data, keyPrefix, contextType) {
559 data[keyPrefix + "Renderer"] = "-";
560 data[keyPrefix + "Version"] = "-";
561 data[keyPrefix + "DriverExtensions"] = "-";
562 data[keyPrefix + "Extensions"] = "-";
563 data[keyPrefix + "WSIInfo"] = "-";
567 let canvas = doc.createElement("canvas");
573 let creationError = null;
575 canvas.addEventListener(
576 "webglcontextcreationerror",
579 creationError = e.statusMessage;
585 gl = canvas.getContext(contextType);
587 if (!creationError) {
588 creationError = e.toString();
592 data[keyPrefix + "Renderer"] =
593 creationError || "(no creation error info)";
599 data[keyPrefix + "Extensions"] = gl.getSupportedExtensions().join(" ");
603 let ext = gl.getExtension("MOZ_debug");
604 // This extension is unconditionally available to chrome. No need to check.
605 let vendor = ext.getParameter(gl.VENDOR);
606 let renderer = ext.getParameter(gl.RENDERER);
608 data[keyPrefix + "Renderer"] = vendor + " -- " + renderer;
609 data[keyPrefix + "Version"] = ext.getParameter(gl.VERSION);
610 data[keyPrefix + "DriverExtensions"] = ext.getParameter(ext.EXTENSIONS);
611 data[keyPrefix + "WSIInfo"] = ext.getParameter(ext.WSI_INFO);
615 // Eagerly free resources.
616 let loseExt = gl.getExtension("WEBGL_lose_context");
618 loseExt.loseContext();
622 GetWebGLInfo(data, "webgl1", "webgl");
623 GetWebGLInfo(data, "webgl2", "webgl2");
625 let infoInfo = gfxInfo.getInfo();
627 data.info = infoInfo;
630 let failureIndices = {};
632 let failures = gfxInfo.getFailures(failureIndices);
633 if (failures.length) {
634 data.failures = failures;
635 if (failureIndices.value.length == failures.length) {
636 data.indices = failureIndices.value;
640 data.featureLog = gfxInfo.getFeatureLog();
641 data.crashGuards = gfxInfo.getActiveCrashGuards();
646 media: function media(done) {
647 function convertDevices(devices) {
652 for (let i = 0; i < devices.length; ++i) {
653 let device = devices.queryElementAt(i, Ci.nsIAudioDeviceInfo);
656 groupId: device.groupId,
657 vendor: device.vendor,
660 preferred: device.preferred,
661 supportedFormat: device.supportedFormat,
662 defaultFormat: device.defaultFormat,
663 maxChannels: device.maxChannels,
664 defaultRate: device.defaultRate,
665 maxRate: device.maxRate,
666 minRate: device.minRate,
667 maxLatency: device.maxLatency,
668 minLatency: device.minLatency,
675 let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
676 data.currentAudioBackend = winUtils.currentAudioBackend;
677 data.currentMaxAudioChannels = winUtils.currentMaxAudioChannels;
678 data.currentPreferredSampleRate = winUtils.currentPreferredSampleRate;
679 data.audioOutputDevices = convertDevices(
681 .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_OUTPUT)
682 .QueryInterface(Ci.nsIArray)
684 data.audioInputDevices = convertDevices(
686 .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_INPUT)
687 .QueryInterface(Ci.nsIArray)
692 javaScript: function javaScript(done) {
694 let winEnumer = Services.ww.getWindowEnumerator();
695 if (winEnumer.hasMoreElements()) {
696 data.incrementalGCEnabled = winEnumer
698 .windowUtils.isIncrementalGCEnabled();
703 accessibility: function accessibility(done) {
705 data.isActive = Services.appinfo.accessibilityEnabled;
706 // eslint-disable-next-line mozilla/use-default-preference-values
708 data.forceDisabled = Services.prefs.getIntPref(
709 "accessibility.force_disabled"
712 data.handlerUsed = Services.appinfo.accessibleHandlerUsed;
713 data.instantiator = Services.appinfo.accessibilityInstantiator;
717 libraryVersions: function libraryVersions(done) {
719 let verInfo = Cc["@mozilla.org/security/nssversion;1"].getService(
722 for (let prop in verInfo) {
723 let match = /^([^_]+)_((Min)?Version)$/.exec(prop);
725 let verProp = match[2][0].toLowerCase() + match[2].substr(1);
726 data[match[1]] = data[match[1]] || {};
727 data[match[1]][verProp] = verInfo[prop];
733 userJS: function userJS(done) {
734 let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
735 userJSFile.append("user.js");
737 exists: userJSFile.exists() && userJSFile.fileSize > 0,
741 intl: function intl(done) {
742 const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
747 requested: Services.locale.requestedLocales,
748 available: Services.locale.availableLocales,
749 supported: Services.locale.appLocalesAsBCP47,
750 regionalPrefs: Services.locale.regionalPrefsLocales,
751 defaultLocale: Services.locale.defaultLocale,
754 systemLocales: osPrefs.systemLocales,
755 regionalPrefsLocales: osPrefs.regionalPrefsLocales,
761 if (AppConstants.MOZ_CRASHREPORTER) {
762 dataProviders.crashes = function crashes(done) {
763 const { CrashReports } = ChromeUtils.import(
764 "resource://gre/modules/CrashReports.jsm"
766 let reports = CrashReports.getReports();
767 let now = new Date();
768 let reportsNew = reports.filter(
769 report => now - report.date < Troubleshoot.kMaxCrashAge
771 let reportsSubmitted = reportsNew.filter(report => !report.pending);
772 let reportsPendingCount = reportsNew.length - reportsSubmitted.length;
773 let data = { submitted: reportsSubmitted, pending: reportsPendingCount };
778 if (AppConstants.MOZ_SANDBOX) {
779 dataProviders.sandbox = function sandbox(done) {
781 if (AppConstants.unixstyle == "linux") {
785 "hasPrivilegedUserNamespaces",
791 for (let key of keys) {
792 if (Services.sysinfo.hasKey(key)) {
793 data[key] = Services.sysinfo.getPropertyAsBool(key);
797 let reporter = Cc["@mozilla.org/sandbox/syscall-reporter;1"].getService(
798 Ci.mozISandboxReporter
800 const snapshot = reporter.snapshot();
802 for (let index = snapshot.begin; index < snapshot.end; ++index) {
803 let report = snapshot.getElement(index);
804 let { msecAgo, pid, tid, procType, syscall } = report;
806 for (let i = 0; i < report.numArgs; ++i) {
807 args.push(report.getArg(i));
809 syscalls.push({ index, msecAgo, pid, tid, procType, syscall, args });
811 data.syscallLog = syscalls;
814 if (AppConstants.MOZ_SANDBOX) {
815 let sandboxSettings = Cc[
816 "@mozilla.org/sandbox/sandbox-settings;1"
817 ].getService(Ci.mozISandboxSettings);
818 data.contentSandboxLevel = Services.prefs.getIntPref(
819 "security.sandbox.content.level"
821 data.effectiveContentSandboxLevel =
822 sandboxSettings.effectiveContentSandboxLevel;
829 if (AppConstants.ENABLE_REMOTE_AGENT) {
830 dataProviders.remoteAgent = function remoteAgent(done) {
831 const { RemoteAgent } = ChromeUtils.import(
832 "chrome://remote/content/RemoteAgent.jsm"
834 const { listening, scheme, host, port } = RemoteAgent;
837 url = `${scheme}://${host}:${port}/`;
839 done({ listening, url });