michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "Troubleshoot", michael@0: ]; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/AddonManager.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: Cu.import("resource://gre/modules/CrashReports.jsm"); michael@0: #endif michael@0: michael@0: let Experiments; michael@0: try { michael@0: Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments; michael@0: } michael@0: catch (e) { michael@0: } michael@0: michael@0: // We use a preferences whitelist to make sure we only show preferences that michael@0: // are useful for support and won't compromise the user's privacy. Note that michael@0: // entries are *prefixes*: for example, "accessibility." applies to all prefs michael@0: // under the "accessibility.*" branch. michael@0: const PREFS_WHITELIST = [ michael@0: "accessibility.", michael@0: "browser.cache.", michael@0: "browser.display.", michael@0: "browser.fixup.", michael@0: "browser.history_expire_", michael@0: "browser.link.open_newwindow", michael@0: "browser.newtab.url", michael@0: "browser.places.", michael@0: "browser.privatebrowsing.", michael@0: "browser.search.context.loadInBackground", michael@0: "browser.search.log", michael@0: "browser.search.openintab", michael@0: "browser.search.param", michael@0: "browser.search.searchEnginesURL", michael@0: "browser.search.suggest.enabled", michael@0: "browser.search.update", michael@0: "browser.search.useDBForOrder", michael@0: "browser.sessionstore.", michael@0: "browser.startup.homepage", michael@0: "browser.tabs.", michael@0: "browser.urlbar.", michael@0: "browser.zoom.", michael@0: "dom.", michael@0: "extensions.checkCompatibility", michael@0: "extensions.lastAppVersion", michael@0: "font.", michael@0: "general.autoScroll", michael@0: "general.useragent.", michael@0: "gfx.", michael@0: "html5.", michael@0: "image.", michael@0: "javascript.", michael@0: "keyword.", michael@0: "layers.", michael@0: "layout.css.dpi", michael@0: "media.", michael@0: "mousewheel.", michael@0: "network.", michael@0: "permissions.default.image", michael@0: "places.", michael@0: "plugin.", michael@0: "plugins.", michael@0: "print.", michael@0: "privacy.", michael@0: "security.", michael@0: "social.enabled", michael@0: "storage.vacuum.last.", michael@0: "svg.", michael@0: "toolkit.startup.recent_crashes", michael@0: "webgl.", michael@0: ]; michael@0: michael@0: // The blacklist, unlike the whitelist, is a list of regular expressions. michael@0: const PREFS_BLACKLIST = [ michael@0: /^network[.]proxy[.]/, michael@0: /[.]print_to_filename$/, michael@0: ]; michael@0: michael@0: this.Troubleshoot = { michael@0: michael@0: /** michael@0: * Captures a snapshot of data that may help troubleshooters troubleshoot michael@0: * trouble. michael@0: * michael@0: * @param done A function that will be asynchronously called when the michael@0: * snapshot completes. It will be passed the snapshot object. michael@0: */ michael@0: snapshot: function snapshot(done) { michael@0: let snapshot = {}; michael@0: let numPending = Object.keys(dataProviders).length; michael@0: function providerDone(providerName, providerData) { michael@0: snapshot[providerName] = providerData; michael@0: if (--numPending == 0) michael@0: // Ensure that done is always and truly called asynchronously. michael@0: Services.tm.mainThread.dispatch(done.bind(null, snapshot), michael@0: Ci.nsIThread.DISPATCH_NORMAL); michael@0: } michael@0: for (let name in dataProviders) { michael@0: try { michael@0: dataProviders[name](providerDone.bind(null, name)); michael@0: } michael@0: catch (err) { michael@0: let msg = "Troubleshoot data provider failed: " + name + "\n" + err; michael@0: Cu.reportError(msg); michael@0: providerDone(name, msg); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days michael@0: }; michael@0: michael@0: // Each data provider is a name => function mapping. When a snapshot is michael@0: // captured, each provider's function is called, and it's the function's job to michael@0: // generate the provider's data. The function is passed a "done" callback, and michael@0: // when done, it must pass its data to the callback. The resulting snapshot michael@0: // object will contain a name => data entry for each provider. michael@0: let dataProviders = { michael@0: michael@0: application: function application(done) { michael@0: let data = { michael@0: name: Services.appinfo.name, michael@0: version: Services.appinfo.version, michael@0: userAgent: Cc["@mozilla.org/network/protocol;1?name=http"]. michael@0: getService(Ci.nsIHttpProtocolHandler). michael@0: userAgent, michael@0: }; michael@0: try { michael@0: data.vendor = Services.prefs.getCharPref("app.support.vendor"); michael@0: } michael@0: catch (e) {} michael@0: let urlFormatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. michael@0: getService(Ci.nsIURLFormatter); michael@0: try { michael@0: data.supportURL = urlFormatter.formatURLPref("app.support.baseURL"); michael@0: } michael@0: catch (e) {} michael@0: done(data); michael@0: }, michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: crashes: function crashes(done) { michael@0: let reports = CrashReports.getReports(); michael@0: let now = new Date(); michael@0: let reportsNew = reports.filter(report => (now - report.date < Troubleshoot.kMaxCrashAge)); michael@0: let reportsSubmitted = reportsNew.filter(report => (!report.pending)); michael@0: let reportsPendingCount = reportsNew.length - reportsSubmitted.length; michael@0: let data = {submitted : reportsSubmitted, pending : reportsPendingCount}; michael@0: done(data); michael@0: }, michael@0: #endif michael@0: michael@0: extensions: function extensions(done) { michael@0: AddonManager.getAddonsByTypes(["extension"], function (extensions) { michael@0: extensions.sort(function (a, b) { michael@0: if (a.isActive != b.isActive) michael@0: return b.isActive ? 1 : -1; michael@0: let lc = a.name.localeCompare(b.name); michael@0: if (lc != 0) michael@0: return lc; michael@0: if (a.version != b.version) michael@0: return a.version > b.version ? 1 : -1; michael@0: return 0; michael@0: }); michael@0: let props = ["name", "version", "isActive", "id"]; michael@0: done(extensions.map(function (ext) { michael@0: return props.reduce(function (extData, prop) { michael@0: extData[prop] = ext[prop]; michael@0: return extData; michael@0: }, {}); michael@0: })); michael@0: }); michael@0: }, michael@0: michael@0: experiments: function experiments(done) { michael@0: if (Experiments === undefined) { michael@0: done([]); michael@0: return; michael@0: } michael@0: michael@0: // getExperiments promises experiment history michael@0: Experiments.instance().getExperiments().then( michael@0: experiments => done(experiments) michael@0: ); michael@0: }, michael@0: michael@0: modifiedPreferences: function modifiedPreferences(done) { michael@0: function getPref(name) { michael@0: let table = {}; michael@0: table[Ci.nsIPrefBranch.PREF_STRING] = "getCharPref"; michael@0: table[Ci.nsIPrefBranch.PREF_INT] = "getIntPref"; michael@0: table[Ci.nsIPrefBranch.PREF_BOOL] = "getBoolPref"; michael@0: let type = Services.prefs.getPrefType(name); michael@0: if (!(type in table)) michael@0: throw new Error("Unknown preference type " + type + " for " + name); michael@0: return Services.prefs[table[type]](name); michael@0: } michael@0: done(PREFS_WHITELIST.reduce(function (prefs, branch) { michael@0: Services.prefs.getChildList(branch).forEach(function (name) { michael@0: if (Services.prefs.prefHasUserValue(name) && michael@0: !PREFS_BLACKLIST.some(function (re) re.test(name))) michael@0: prefs[name] = getPref(name); michael@0: }); michael@0: return prefs; michael@0: }, {})); michael@0: }, michael@0: michael@0: graphics: function graphics(done) { michael@0: function statusMsgForFeature(feature) { michael@0: // We return an array because in the tryNewerDriver case we need to michael@0: // include the suggested version, which the consumer likely needs to plug michael@0: // into a format string from a localization file. Rather than returning michael@0: // a string in some cases and an array in others, return an array always. michael@0: let msg = [""]; michael@0: try { michael@0: var status = gfxInfo.getFeatureStatus(feature); michael@0: } michael@0: catch (e) {} michael@0: switch (status) { michael@0: case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE: michael@0: case Ci.nsIGfxInfo.FEATURE_DISCOURAGED: michael@0: msg = ["blockedGfxCard"]; michael@0: break; michael@0: case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION: michael@0: msg = ["blockedOSVersion"]; michael@0: break; michael@0: case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION: michael@0: try { michael@0: var suggestedDriverVersion = michael@0: gfxInfo.getFeatureSuggestedDriverVersion(feature); michael@0: } michael@0: catch (e) {} michael@0: msg = suggestedDriverVersion ? michael@0: ["tryNewerDriver", suggestedDriverVersion] : michael@0: ["blockedDriver"]; michael@0: break; michael@0: } michael@0: return msg; michael@0: } michael@0: michael@0: let data = {}; michael@0: michael@0: try { michael@0: // nsIGfxInfo may not be implemented on some platforms. michael@0: var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); michael@0: } michael@0: catch (e) {} michael@0: michael@0: data.numTotalWindows = 0; michael@0: data.numAcceleratedWindows = 0; michael@0: let winEnumer = Services.ww.getWindowEnumerator(); michael@0: while (winEnumer.hasMoreElements()) { michael@0: data.numTotalWindows++; michael@0: let winUtils = winEnumer.getNext(). michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils); michael@0: try { michael@0: data.windowLayerManagerType = winUtils.layerManagerType; michael@0: data.windowLayerManagerRemote = winUtils.layerManagerRemote; michael@0: } michael@0: catch (e) { michael@0: continue; michael@0: } michael@0: if (data.windowLayerManagerType != "Basic") michael@0: data.numAcceleratedWindows++; michael@0: } michael@0: michael@0: if (!data.numAcceleratedWindows && gfxInfo) { michael@0: let feature = michael@0: #ifdef XP_WIN michael@0: gfxInfo.FEATURE_DIRECT3D_9_LAYERS; michael@0: #else michael@0: gfxInfo.FEATURE_OPENGL_LAYERS; michael@0: #endif michael@0: data.numAcceleratedWindowsMessage = statusMsgForFeature(feature); michael@0: } michael@0: michael@0: if (!gfxInfo) { michael@0: done(data); michael@0: return; michael@0: } michael@0: michael@0: // keys are the names of attributes on nsIGfxInfo, values become the names michael@0: // of the corresponding properties in our data object. A null value means michael@0: // no change. This is needed so that the names of properties in the data michael@0: // object are the same as the names of keys in aboutSupport.properties. michael@0: let gfxInfoProps = { michael@0: adapterDescription: null, michael@0: adapterVendorID: null, michael@0: adapterDeviceID: null, michael@0: adapterRAM: null, michael@0: adapterDriver: "adapterDrivers", michael@0: adapterDriverVersion: "driverVersion", michael@0: adapterDriverDate: "driverDate", michael@0: michael@0: adapterDescription2: null, michael@0: adapterVendorID2: null, michael@0: adapterDeviceID2: null, michael@0: adapterRAM2: null, michael@0: adapterDriver2: "adapterDrivers2", michael@0: adapterDriverVersion2: "driverVersion2", michael@0: adapterDriverDate2: "driverDate2", michael@0: isGPU2Active: null, michael@0: michael@0: D2DEnabled: "direct2DEnabled", michael@0: DWriteEnabled: "directWriteEnabled", michael@0: DWriteVersion: "directWriteVersion", michael@0: cleartypeParameters: "clearTypeParameters", michael@0: }; michael@0: michael@0: for (let prop in gfxInfoProps) { michael@0: try { michael@0: data[gfxInfoProps[prop] || prop] = gfxInfo[prop]; michael@0: } michael@0: catch (e) {} michael@0: } michael@0: michael@0: if (("direct2DEnabled" in data) && !data.direct2DEnabled) michael@0: data.direct2DEnabledMessage = michael@0: statusMsgForFeature(Ci.nsIGfxInfo.FEATURE_DIRECT2D); michael@0: michael@0: let doc = michael@0: Cc["@mozilla.org/xmlextras/domparser;1"] michael@0: .createInstance(Ci.nsIDOMParser) michael@0: .parseFromString("", "text/html"); michael@0: michael@0: let canvas = doc.createElement("canvas"); michael@0: canvas.width = 1; michael@0: canvas.height = 1; michael@0: michael@0: let gl; michael@0: try { michael@0: gl = canvas.getContext("experimental-webgl"); michael@0: } catch(e) {} michael@0: michael@0: if (gl) { michael@0: let ext = gl.getExtension("WEBGL_debug_renderer_info"); michael@0: // this extension is unconditionally available to chrome. No need to check. michael@0: data.webglRenderer = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) michael@0: + " -- " michael@0: + gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); michael@0: } else { michael@0: let feature = michael@0: #ifdef XP_WIN michael@0: // If ANGLE is not available but OpenGL is, we want to report on the michael@0: // OpenGL feature, because that's what's going to get used. In all michael@0: // other cases we want to report on the ANGLE feature. michael@0: gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE) != michael@0: Ci.nsIGfxInfo.FEATURE_NO_INFO && michael@0: gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL) == michael@0: Ci.nsIGfxInfo.FEATURE_NO_INFO ? michael@0: Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL : michael@0: Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE; michael@0: #else michael@0: Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL; michael@0: #endif michael@0: data.webglRendererMessage = statusMsgForFeature(feature); michael@0: } michael@0: michael@0: let infoInfo = gfxInfo.getInfo(); michael@0: if (infoInfo) michael@0: data.info = infoInfo; michael@0: michael@0: let failures = gfxInfo.getFailures(); michael@0: if (failures.length) michael@0: data.failures = failures; michael@0: michael@0: done(data); michael@0: }, michael@0: michael@0: javaScript: function javaScript(done) { michael@0: let data = {}; michael@0: let winEnumer = Services.ww.getWindowEnumerator(); michael@0: if (winEnumer.hasMoreElements()) michael@0: data.incrementalGCEnabled = winEnumer.getNext(). michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils). michael@0: isIncrementalGCEnabled(); michael@0: done(data); michael@0: }, michael@0: michael@0: accessibility: function accessibility(done) { michael@0: let data = {}; michael@0: try { michael@0: data.isActive = Components.manager.QueryInterface(Ci.nsIServiceManager). michael@0: isServiceInstantiatedByContractID( michael@0: "@mozilla.org/accessibilityService;1", michael@0: Ci.nsISupports); michael@0: } michael@0: catch (e) { michael@0: data.isActive = false; michael@0: } michael@0: try { michael@0: data.forceDisabled = michael@0: Services.prefs.getIntPref("accessibility.force_disabled"); michael@0: } michael@0: catch (e) {} michael@0: done(data); michael@0: }, michael@0: michael@0: libraryVersions: function libraryVersions(done) { michael@0: let data = {}; michael@0: let verInfo = Cc["@mozilla.org/security/nssversion;1"]. michael@0: getService(Ci.nsINSSVersion); michael@0: for (let prop in verInfo) { michael@0: let match = /^([^_]+)_((Min)?Version)$/.exec(prop); michael@0: if (match) { michael@0: let verProp = match[2][0].toLowerCase() + match[2].substr(1); michael@0: data[match[1]] = data[match[1]] || {}; michael@0: data[match[1]][verProp] = verInfo[prop]; michael@0: } michael@0: } michael@0: done(data); michael@0: }, michael@0: michael@0: userJS: function userJS(done) { michael@0: let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile); michael@0: userJSFile.append("user.js"); michael@0: done({ michael@0: exists: userJSFile.exists() && userJSFile.fileSize > 0, michael@0: }); michael@0: }, michael@0: };