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 = [
31 "browser.download.folderList",
32 "browser.download.hide_plugins_without_extensions",
33 "browser.download.lastDir.savePerSite",
34 "browser.download.manager.addToRecentDocs",
35 "browser.download.manager.resumeOnWakeDelay",
36 "browser.download.preferred.",
37 "browser.download.skipConfirmLaunchExecutable",
38 "browser.download.useDownloadDir",
40 "browser.history_expire_",
41 "browser.link.open_newwindow",
43 "browser.privatebrowsing.",
44 "browser.search.context.loadInBackground",
46 "browser.search.openintab",
47 "browser.search.param",
48 "browser.search.searchEnginesURL",
49 "browser.search.suggest.enabled",
50 "browser.search.update",
51 "browser.search.useDBForOrder",
52 "browser.sessionstore.",
53 "browser.startup.homepage",
58 "extensions.checkCompatibility",
59 "extensions.formautofill.",
60 "extensions.lastAppVersion",
66 "identity.fxaccounts.enabled",
73 "layout.display-list.",
77 "permissions.default.image",
84 "services.sync.declinedEngines",
85 "services.sync.lastPing",
86 "services.sync.lastSync",
87 "services.sync.numClients",
88 "services.sync.engine.",
90 "storage.vacuum.last.",
92 "toolkit.startup.recent_crashes",
94 "ui.osk.detect_physical_keyboard",
95 "ui.osk.require_tablet_mode",
96 "ui.osk.debug.keyboardDisplayReason",
100 // The blacklist, unlike the whitelist, is a list of regular expressions.
101 const PREFS_BLACKLIST = [
102 /^media[.]webrtc[.]debug[.]aec_log_dir/,
103 /^media[.]webrtc[.]debug[.]log_file/,
104 /^network[.]proxy[.]/,
105 /[.]print_to_filename$/,
106 /^print[.]macosx[.]pagesetup/,
110 // Table of getters for various preference types.
111 const PREFS_GETTERS = {};
113 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_STRING] = (prefs, name) =>
114 prefs.getStringPref(name);
115 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_INT] = (prefs, name) =>
116 prefs.getIntPref(name);
117 PREFS_GETTERS[Ci.nsIPrefBranch.PREF_BOOL] = (prefs, name) =>
118 prefs.getBoolPref(name);
120 const kURLDecorationPref = "privacy.restrict3rdpartystorage.url_decorations";
122 // Return the preferences filtered by PREFS_BLACKLIST and PREFS_WHITELIST lists
123 // and also by the custom 'filter'-ing function.
124 function getPrefList(filter) {
125 filter = filter || (name => true);
126 function getPref(name) {
127 let type = Services.prefs.getPrefType(name);
128 if (!(type in PREFS_GETTERS)) {
129 throw new Error("Unknown preference type " + type + " for " + name);
131 return PREFS_GETTERS[type](Services.prefs, name);
134 return PREFS_WHITELIST.reduce(function(prefs, branch) {
135 Services.prefs.getChildList(branch).forEach(function(name) {
136 if (filter(name) && !PREFS_BLACKLIST.some(re => re.test(name))) {
137 prefs[name] = getPref(name);
146 * Captures a snapshot of data that may help troubleshooters troubleshoot
149 * @param done A function that will be asynchronously called when the
150 * snapshot completes. It will be passed the snapshot object.
152 snapshot: function snapshot(done) {
154 let numPending = Object.keys(dataProviders).length;
155 function providerDone(providerName, providerData) {
156 snapshot[providerName] = providerData;
157 if (--numPending == 0) {
158 // Ensure that done is always and truly called asynchronously.
159 Services.tm.dispatchToMainThread(done.bind(null, snapshot));
162 for (let name in dataProviders) {
164 dataProviders[name](providerDone.bind(null, name));
166 let msg = "Troubleshoot data provider failed: " + name + "\n" + err;
168 providerDone(name, msg);
173 kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days
176 // Each data provider is a name => function mapping. When a snapshot is
177 // captured, each provider's function is called, and it's the function's job to
178 // generate the provider's data. The function is passed a "done" callback, and
179 // when done, it must pass its data to the callback. The resulting snapshot
180 // object will contain a name => data entry for each provider.
181 var dataProviders = {
182 application: function application(done) {
184 name: Services.appinfo.name,
186 Services.sysinfo.getProperty("name") +
188 Services.sysinfo.getProperty("version"),
189 version: AppConstants.MOZ_APP_VERSION_DISPLAY,
190 buildID: Services.appinfo.appBuildID,
191 userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].getService(
192 Ci.nsIHttpProtocolHandler
194 safeMode: Services.appinfo.inSafeMode,
197 if (AppConstants.MOZ_UPDATER) {
198 data.updateChannel = ChromeUtils.import(
199 "resource://gre/modules/UpdateUtils.jsm",
201 ).UpdateUtils.UpdateChannel;
204 // eslint-disable-next-line mozilla/use-default-preference-values
206 data.vendor = Services.prefs.getCharPref("app.support.vendor");
209 data.supportURL = Services.urlFormatter.formatURLPref(
210 "app.support.baseURL"
214 data.numTotalWindows = 0;
215 data.numRemoteWindows = 0;
216 for (let { docShell } of Services.wm.getEnumerator("navigator:browser")) {
217 data.numTotalWindows++;
218 let remote = docShell.QueryInterface(Ci.nsILoadContext).useRemoteTabs;
220 data.numRemoteWindows++;
225 data.launcherProcessState = Services.appinfo.launcherProcessState;
228 data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart;
231 let e10sStatus = Cc["@mozilla.org/supports-PRUint64;1"].createInstance(
232 Ci.nsISupportsPRUint64
234 let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
235 appinfo.observe(e10sStatus, "getE10SBlocked", "");
236 data.autoStartStatus = e10sStatus.data;
238 data.autoStartStatus = -1;
241 if (Services.policies) {
242 data.policiesStatus = Services.policies.status;
245 const keyLocationServiceGoogle = Services.urlFormatter
246 .formatURL("%GOOGLE_LOCATION_SERVICE_API_KEY%")
248 data.keyLocationServiceGoogleFound =
249 keyLocationServiceGoogle != "no-google-location-service-api-key" &&
250 !!keyLocationServiceGoogle.length;
252 const keySafebrowsingGoogle = Services.urlFormatter
253 .formatURL("%GOOGLE_SAFEBROWSING_API_KEY%")
255 data.keySafebrowsingGoogleFound =
256 keySafebrowsingGoogle != "no-google-safebrowsing-api-key" &&
257 !!keySafebrowsingGoogle.length;
259 const keyMozilla = Services.urlFormatter
260 .formatURL("%MOZILLA_API_KEY%")
262 data.keyMozillaFound =
263 keyMozilla != "no-mozilla-api-key" && !!keyMozilla.length;
268 extensions: async function extensions(done) {
269 let extensions = await AddonManager.getAddonsByTypes(["extension"]);
270 extensions = extensions.filter(e => !e.isSystem);
271 extensions.sort(function(a, b) {
272 if (a.isActive != b.isActive) {
273 return b.isActive ? 1 : -1;
276 // In some unfortunate cases addon names can be null.
277 let aname = a.name || "";
278 let bname = b.name || "";
279 let lc = aname.localeCompare(bname);
283 if (a.version != b.version) {
284 return a.version > b.version ? 1 : -1;
288 let props = ["name", "version", "isActive", "id"];
290 extensions.map(function(ext) {
291 return props.reduce(function(extData, prop) {
292 extData[prop] = ext[prop];
299 securitySoftware: function securitySoftware(done) {
302 let sysInfo = Cc["@mozilla.org/system-info;1"].getService(
307 "registeredAntiVirus",
308 "registeredAntiSpyware",
309 "registeredFirewall",
311 for (let key of keys) {
314 prop = sysInfo.getProperty(key);
323 features: async function features(done) {
324 let features = await AddonManager.getAddonsByTypes(["extension"]);
325 features = features.filter(f => f.isSystem);
326 features.sort(function(a, b) {
327 // In some unfortunate cases addon names can be null.
328 let aname = a.name || null;
329 let bname = b.name || null;
330 let lc = aname.localeCompare(bname);
334 if (a.version != b.version) {
335 return a.version > b.version ? 1 : -1;
339 let props = ["name", "version", "id"];
341 features.map(function(f) {
342 return props.reduce(function(fData, prop) {
343 fData[prop] = f[prop];
350 processes: function processes(done) {
351 let remoteTypes = {};
353 for (let i = 0; i < Services.ppmm.childCount; i++) {
356 remoteType = Services.ppmm.getChildAt(i).remoteType;
359 // The parent process is also managed by the ppmm (because
360 // of non-remote tabs), but it doesn't have a remoteType.
365 remoteType = E10SUtils.remoteTypePrefix(remoteType);
367 if (remoteTypes[remoteType]) {
368 remoteTypes[remoteType]++;
370 remoteTypes[remoteType] = 1;
375 let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
376 if (winUtils.gpuProcessPid != -1) {
383 maxWebContentProcesses: Services.appinfo.maxWebProcessCount,
389 modifiedPreferences: function modifiedPreferences(done) {
390 done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
393 lockedPreferences: function lockedPreferences(done) {
394 // The URL Decoration pref isn't an important locked pref, so there is no
395 // good reason to report it.
398 name => name != kURLDecorationPref && Services.prefs.prefIsLocked(name)
403 graphics: function graphics(done) {
404 function statusMsgForFeature(feature) {
405 // We return an object because in the try-newer-driver case we need to
406 // include the suggested version, which the consumer likely needs to plug
407 // into a format string from a localization file. Rather than returning
408 // a string in some cases and an object in others, return an object always.
409 let msg = { key: "" };
411 var status = gfxInfo.getFeatureStatus(feature);
414 case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE:
415 case Ci.nsIGfxInfo.FEATURE_DISCOURAGED:
416 msg = { key: "blocked-gfx-card" };
418 case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION:
419 msg = { key: "blocked-os-version" };
421 case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION:
423 var driverVersion = gfxInfo.getFeatureSuggestedDriverVersion(
428 ? { key: "try-newer-driver", args: { driverVersion } }
429 : { key: "blocked-driver" };
431 case Ci.nsIGfxInfo.FEATURE_BLOCKED_MISMATCHED_VERSION:
432 msg = { key: "blocked-mismatched-version" };
441 // nsIGfxInfo may not be implemented on some platforms.
442 var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
446 // done will be called upon all pending promises being resolved.
447 // add your pending promise to promises when adding new ones.
448 function completed() {
449 Promise.all(promises).then(() => done(data));
452 data.numTotalWindows = 0;
453 data.numAcceleratedWindows = 0;
454 for (let win of Services.ww.getWindowEnumerator()) {
455 let winUtils = win.windowUtils;
457 // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
459 winUtils.layerManagerType == "None" ||
460 !winUtils.layerManagerRemote
464 data.numTotalWindows++;
465 data.windowLayerManagerType = winUtils.layerManagerType;
466 data.windowLayerManagerRemote = winUtils.layerManagerRemote;
467 data.windowUsingAdvancedLayers = winUtils.usingAdvancedLayers;
471 if (data.windowLayerManagerType != "Basic") {
472 data.numAcceleratedWindows++;
476 // If we had no OMTC windows, report back Basic Layers.
477 if (!data.windowLayerManagerType) {
478 data.windowLayerManagerType = "Basic";
479 data.windowLayerManagerRemote = false;
482 if (!data.numAcceleratedWindows && gfxInfo) {
483 let win = AppConstants.platform == "win";
485 ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS
486 : gfxInfo.FEATURE_OPENGL_LAYERS;
487 data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
495 // keys are the names of attributes on nsIGfxInfo, values become the names
496 // of the corresponding properties in our data object. A null value means
497 // no change. This is needed so that the names of properties in the data
498 // object are the same as the names of keys in aboutSupport.properties.
500 adapterDescription: null,
501 adapterVendorID: null,
502 adapterDeviceID: null,
503 adapterSubsysID: null,
505 adapterDriver: "adapterDrivers",
506 adapterDriverVendor: "driverVendor",
507 adapterDriverVersion: "driverVersion",
508 adapterDriverDate: "driverDate",
510 adapterDescription2: null,
511 adapterVendorID2: null,
512 adapterDeviceID2: null,
513 adapterSubsysID2: null,
515 adapterDriver2: "adapterDrivers2",
516 adapterDriverVendor2: "driverVendor2",
517 adapterDriverVersion2: "driverVersion2",
518 adapterDriverDate2: "driverDate2",
521 D2DEnabled: "direct2DEnabled",
522 DWriteEnabled: "directWriteEnabled",
523 DWriteVersion: "directWriteVersion",
524 cleartypeParameters: "clearTypeParameters",
525 UsesTiling: "usesTiling",
526 ContentUsesTiling: "contentUsesTiling",
527 OffMainThreadPaintEnabled: "offMainThreadPaintEnabled",
528 OffMainThreadPaintWorkerCount: "offMainThreadPaintWorkerCount",
529 TargetFrameRate: "targetFrameRate",
530 windowProtocol: null,
533 for (let prop in gfxInfoProps) {
535 data[gfxInfoProps[prop] || prop] = gfxInfo[prop];
539 if ("direct2DEnabled" in data && !data.direct2DEnabled) {
540 data.direct2DEnabledMessage = statusMsgForFeature(
541 Ci.nsIGfxInfo.FEATURE_DIRECT2D
545 let doc = new DOMParser().parseFromString("<html/>", "text/html");
547 function GetWebGLInfo(data, keyPrefix, contextType) {
548 data[keyPrefix + "Renderer"] = "-";
549 data[keyPrefix + "Version"] = "-";
550 data[keyPrefix + "DriverExtensions"] = "-";
551 data[keyPrefix + "Extensions"] = "-";
552 data[keyPrefix + "WSIInfo"] = "-";
556 let canvas = doc.createElement("canvas");
562 let creationError = null;
564 canvas.addEventListener(
565 "webglcontextcreationerror",
568 creationError = e.statusMessage;
574 gl = canvas.getContext(contextType);
576 if (!creationError) {
577 creationError = e.toString();
581 data[keyPrefix + "Renderer"] =
582 creationError || "(no creation error info)";
588 data[keyPrefix + "Extensions"] = gl.getSupportedExtensions().join(" ");
592 let ext = gl.getExtension("MOZ_debug");
593 // This extension is unconditionally available to chrome. No need to check.
594 let vendor = ext.getParameter(gl.VENDOR);
595 let renderer = ext.getParameter(gl.RENDERER);
597 data[keyPrefix + "Renderer"] = vendor + " -- " + renderer;
598 data[keyPrefix + "Version"] = ext.getParameter(gl.VERSION);
599 data[keyPrefix + "DriverExtensions"] = ext.getParameter(ext.EXTENSIONS);
600 data[keyPrefix + "WSIInfo"] = ext.getParameter(ext.WSI_INFO);
604 // Eagerly free resources.
605 let loseExt = gl.getExtension("WEBGL_lose_context");
607 loseExt.loseContext();
611 GetWebGLInfo(data, "webgl1", "webgl");
612 GetWebGLInfo(data, "webgl2", "webgl2");
614 let infoInfo = gfxInfo.getInfo();
616 data.info = infoInfo;
619 let failureIndices = {};
621 let failures = gfxInfo.getFailures(failureIndices);
622 if (failures.length) {
623 data.failures = failures;
624 if (failureIndices.value.length == failures.length) {
625 data.indices = failureIndices.value;
629 data.featureLog = gfxInfo.getFeatureLog();
630 data.crashGuards = gfxInfo.getActiveCrashGuards();
635 media: function media(done) {
636 function convertDevices(devices) {
641 for (let i = 0; i < devices.length; ++i) {
642 let device = devices.queryElementAt(i, Ci.nsIAudioDeviceInfo);
645 groupId: device.groupId,
646 vendor: device.vendor,
649 preferred: device.preferred,
650 supportedFormat: device.supportedFormat,
651 defaultFormat: device.defaultFormat,
652 maxChannels: device.maxChannels,
653 defaultRate: device.defaultRate,
654 maxRate: device.maxRate,
655 minRate: device.minRate,
656 maxLatency: device.maxLatency,
657 minLatency: device.minLatency,
664 let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
665 data.currentAudioBackend = winUtils.currentAudioBackend;
666 data.currentMaxAudioChannels = winUtils.currentMaxAudioChannels;
667 data.currentPreferredSampleRate = winUtils.currentPreferredSampleRate;
668 data.audioOutputDevices = convertDevices(
670 .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_OUTPUT)
671 .QueryInterface(Ci.nsIArray)
673 data.audioInputDevices = convertDevices(
675 .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_INPUT)
676 .QueryInterface(Ci.nsIArray)
681 javaScript: function javaScript(done) {
683 let winEnumer = Services.ww.getWindowEnumerator();
684 if (winEnumer.hasMoreElements()) {
685 data.incrementalGCEnabled = winEnumer
687 .windowUtils.isIncrementalGCEnabled();
692 accessibility: function accessibility(done) {
694 data.isActive = Services.appinfo.accessibilityEnabled;
695 // eslint-disable-next-line mozilla/use-default-preference-values
697 data.forceDisabled = Services.prefs.getIntPref(
698 "accessibility.force_disabled"
701 data.handlerUsed = Services.appinfo.accessibleHandlerUsed;
702 data.instantiator = Services.appinfo.accessibilityInstantiator;
706 libraryVersions: function libraryVersions(done) {
708 let verInfo = Cc["@mozilla.org/security/nssversion;1"].getService(
711 for (let prop in verInfo) {
712 let match = /^([^_]+)_((Min)?Version)$/.exec(prop);
714 let verProp = match[2][0].toLowerCase() + match[2].substr(1);
715 data[match[1]] = data[match[1]] || {};
716 data[match[1]][verProp] = verInfo[prop];
722 userJS: function userJS(done) {
723 let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
724 userJSFile.append("user.js");
726 exists: userJSFile.exists() && userJSFile.fileSize > 0,
730 intl: function intl(done) {
731 const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
736 requested: Services.locale.requestedLocales,
737 available: Services.locale.availableLocales,
738 supported: Services.locale.appLocalesAsBCP47,
739 regionalPrefs: Services.locale.regionalPrefsLocales,
740 defaultLocale: Services.locale.defaultLocale,
743 systemLocales: osPrefs.systemLocales,
744 regionalPrefsLocales: osPrefs.regionalPrefsLocales,
750 if (AppConstants.MOZ_CRASHREPORTER) {
751 dataProviders.crashes = function crashes(done) {
752 const { CrashReports } = ChromeUtils.import(
753 "resource://gre/modules/CrashReports.jsm"
755 let reports = CrashReports.getReports();
756 let now = new Date();
757 let reportsNew = reports.filter(
758 report => now - report.date < Troubleshoot.kMaxCrashAge
760 let reportsSubmitted = reportsNew.filter(report => !report.pending);
761 let reportsPendingCount = reportsNew.length - reportsSubmitted.length;
762 let data = { submitted: reportsSubmitted, pending: reportsPendingCount };
767 if (AppConstants.MOZ_SANDBOX) {
768 dataProviders.sandbox = function sandbox(done) {
770 if (AppConstants.platform == "linux") {
774 "hasPrivilegedUserNamespaces",
780 for (let key of keys) {
781 if (Services.sysinfo.hasKey(key)) {
782 data[key] = Services.sysinfo.getPropertyAsBool(key);
786 let reporter = Cc["@mozilla.org/sandbox/syscall-reporter;1"].getService(
787 Ci.mozISandboxReporter
789 const snapshot = reporter.snapshot();
791 for (let index = snapshot.begin; index < snapshot.end; ++index) {
792 let report = snapshot.getElement(index);
793 let { msecAgo, pid, tid, procType, syscall } = report;
795 for (let i = 0; i < report.numArgs; ++i) {
796 args.push(report.getArg(i));
798 syscalls.push({ index, msecAgo, pid, tid, procType, syscall, args });
800 data.syscallLog = syscalls;
803 if (AppConstants.MOZ_SANDBOX) {
804 let sandboxSettings = Cc[
805 "@mozilla.org/sandbox/sandbox-settings;1"
806 ].getService(Ci.mozISandboxSettings);
807 data.contentSandboxLevel = Services.prefs.getIntPref(
808 "security.sandbox.content.level"
810 data.effectiveContentSandboxLevel =
811 sandboxSettings.effectiveContentSandboxLevel;