|
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/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = [ |
|
6 "Troubleshoot", |
|
7 ]; |
|
8 |
|
9 const { classes: Cc, interfaces: Ci, utils: Cu } = Components; |
|
10 |
|
11 Cu.import("resource://gre/modules/AddonManager.jsm"); |
|
12 Cu.import("resource://gre/modules/Services.jsm"); |
|
13 |
|
14 #ifdef MOZ_CRASHREPORTER |
|
15 Cu.import("resource://gre/modules/CrashReports.jsm"); |
|
16 #endif |
|
17 |
|
18 let Experiments; |
|
19 try { |
|
20 Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments; |
|
21 } |
|
22 catch (e) { |
|
23 } |
|
24 |
|
25 // We use a preferences whitelist to make sure we only show preferences that |
|
26 // are useful for support and won't compromise the user's privacy. Note that |
|
27 // entries are *prefixes*: for example, "accessibility." applies to all prefs |
|
28 // under the "accessibility.*" branch. |
|
29 const PREFS_WHITELIST = [ |
|
30 "accessibility.", |
|
31 "browser.cache.", |
|
32 "browser.display.", |
|
33 "browser.fixup.", |
|
34 "browser.history_expire_", |
|
35 "browser.link.open_newwindow", |
|
36 "browser.newtab.url", |
|
37 "browser.places.", |
|
38 "browser.privatebrowsing.", |
|
39 "browser.search.context.loadInBackground", |
|
40 "browser.search.log", |
|
41 "browser.search.openintab", |
|
42 "browser.search.param", |
|
43 "browser.search.searchEnginesURL", |
|
44 "browser.search.suggest.enabled", |
|
45 "browser.search.update", |
|
46 "browser.search.useDBForOrder", |
|
47 "browser.sessionstore.", |
|
48 "browser.startup.homepage", |
|
49 "browser.tabs.", |
|
50 "browser.urlbar.", |
|
51 "browser.zoom.", |
|
52 "dom.", |
|
53 "extensions.checkCompatibility", |
|
54 "extensions.lastAppVersion", |
|
55 "font.", |
|
56 "general.autoScroll", |
|
57 "general.useragent.", |
|
58 "gfx.", |
|
59 "html5.", |
|
60 "image.", |
|
61 "javascript.", |
|
62 "keyword.", |
|
63 "layers.", |
|
64 "layout.css.dpi", |
|
65 "media.", |
|
66 "mousewheel.", |
|
67 "network.", |
|
68 "permissions.default.image", |
|
69 "places.", |
|
70 "plugin.", |
|
71 "plugins.", |
|
72 "print.", |
|
73 "privacy.", |
|
74 "security.", |
|
75 "social.enabled", |
|
76 "storage.vacuum.last.", |
|
77 "svg.", |
|
78 "toolkit.startup.recent_crashes", |
|
79 "webgl.", |
|
80 ]; |
|
81 |
|
82 // The blacklist, unlike the whitelist, is a list of regular expressions. |
|
83 const PREFS_BLACKLIST = [ |
|
84 /^network[.]proxy[.]/, |
|
85 /[.]print_to_filename$/, |
|
86 ]; |
|
87 |
|
88 this.Troubleshoot = { |
|
89 |
|
90 /** |
|
91 * Captures a snapshot of data that may help troubleshooters troubleshoot |
|
92 * trouble. |
|
93 * |
|
94 * @param done A function that will be asynchronously called when the |
|
95 * snapshot completes. It will be passed the snapshot object. |
|
96 */ |
|
97 snapshot: function snapshot(done) { |
|
98 let snapshot = {}; |
|
99 let numPending = Object.keys(dataProviders).length; |
|
100 function providerDone(providerName, providerData) { |
|
101 snapshot[providerName] = providerData; |
|
102 if (--numPending == 0) |
|
103 // Ensure that done is always and truly called asynchronously. |
|
104 Services.tm.mainThread.dispatch(done.bind(null, snapshot), |
|
105 Ci.nsIThread.DISPATCH_NORMAL); |
|
106 } |
|
107 for (let name in dataProviders) { |
|
108 try { |
|
109 dataProviders[name](providerDone.bind(null, name)); |
|
110 } |
|
111 catch (err) { |
|
112 let msg = "Troubleshoot data provider failed: " + name + "\n" + err; |
|
113 Cu.reportError(msg); |
|
114 providerDone(name, msg); |
|
115 } |
|
116 } |
|
117 }, |
|
118 |
|
119 kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days |
|
120 }; |
|
121 |
|
122 // Each data provider is a name => function mapping. When a snapshot is |
|
123 // captured, each provider's function is called, and it's the function's job to |
|
124 // generate the provider's data. The function is passed a "done" callback, and |
|
125 // when done, it must pass its data to the callback. The resulting snapshot |
|
126 // object will contain a name => data entry for each provider. |
|
127 let dataProviders = { |
|
128 |
|
129 application: function application(done) { |
|
130 let data = { |
|
131 name: Services.appinfo.name, |
|
132 version: Services.appinfo.version, |
|
133 userAgent: Cc["@mozilla.org/network/protocol;1?name=http"]. |
|
134 getService(Ci.nsIHttpProtocolHandler). |
|
135 userAgent, |
|
136 }; |
|
137 try { |
|
138 data.vendor = Services.prefs.getCharPref("app.support.vendor"); |
|
139 } |
|
140 catch (e) {} |
|
141 let urlFormatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. |
|
142 getService(Ci.nsIURLFormatter); |
|
143 try { |
|
144 data.supportURL = urlFormatter.formatURLPref("app.support.baseURL"); |
|
145 } |
|
146 catch (e) {} |
|
147 done(data); |
|
148 }, |
|
149 |
|
150 #ifdef MOZ_CRASHREPORTER |
|
151 crashes: function crashes(done) { |
|
152 let reports = CrashReports.getReports(); |
|
153 let now = new Date(); |
|
154 let reportsNew = reports.filter(report => (now - report.date < Troubleshoot.kMaxCrashAge)); |
|
155 let reportsSubmitted = reportsNew.filter(report => (!report.pending)); |
|
156 let reportsPendingCount = reportsNew.length - reportsSubmitted.length; |
|
157 let data = {submitted : reportsSubmitted, pending : reportsPendingCount}; |
|
158 done(data); |
|
159 }, |
|
160 #endif |
|
161 |
|
162 extensions: function extensions(done) { |
|
163 AddonManager.getAddonsByTypes(["extension"], function (extensions) { |
|
164 extensions.sort(function (a, b) { |
|
165 if (a.isActive != b.isActive) |
|
166 return b.isActive ? 1 : -1; |
|
167 let lc = a.name.localeCompare(b.name); |
|
168 if (lc != 0) |
|
169 return lc; |
|
170 if (a.version != b.version) |
|
171 return a.version > b.version ? 1 : -1; |
|
172 return 0; |
|
173 }); |
|
174 let props = ["name", "version", "isActive", "id"]; |
|
175 done(extensions.map(function (ext) { |
|
176 return props.reduce(function (extData, prop) { |
|
177 extData[prop] = ext[prop]; |
|
178 return extData; |
|
179 }, {}); |
|
180 })); |
|
181 }); |
|
182 }, |
|
183 |
|
184 experiments: function experiments(done) { |
|
185 if (Experiments === undefined) { |
|
186 done([]); |
|
187 return; |
|
188 } |
|
189 |
|
190 // getExperiments promises experiment history |
|
191 Experiments.instance().getExperiments().then( |
|
192 experiments => done(experiments) |
|
193 ); |
|
194 }, |
|
195 |
|
196 modifiedPreferences: function modifiedPreferences(done) { |
|
197 function getPref(name) { |
|
198 let table = {}; |
|
199 table[Ci.nsIPrefBranch.PREF_STRING] = "getCharPref"; |
|
200 table[Ci.nsIPrefBranch.PREF_INT] = "getIntPref"; |
|
201 table[Ci.nsIPrefBranch.PREF_BOOL] = "getBoolPref"; |
|
202 let type = Services.prefs.getPrefType(name); |
|
203 if (!(type in table)) |
|
204 throw new Error("Unknown preference type " + type + " for " + name); |
|
205 return Services.prefs[table[type]](name); |
|
206 } |
|
207 done(PREFS_WHITELIST.reduce(function (prefs, branch) { |
|
208 Services.prefs.getChildList(branch).forEach(function (name) { |
|
209 if (Services.prefs.prefHasUserValue(name) && |
|
210 !PREFS_BLACKLIST.some(function (re) re.test(name))) |
|
211 prefs[name] = getPref(name); |
|
212 }); |
|
213 return prefs; |
|
214 }, {})); |
|
215 }, |
|
216 |
|
217 graphics: function graphics(done) { |
|
218 function statusMsgForFeature(feature) { |
|
219 // We return an array because in the tryNewerDriver case we need to |
|
220 // include the suggested version, which the consumer likely needs to plug |
|
221 // into a format string from a localization file. Rather than returning |
|
222 // a string in some cases and an array in others, return an array always. |
|
223 let msg = [""]; |
|
224 try { |
|
225 var status = gfxInfo.getFeatureStatus(feature); |
|
226 } |
|
227 catch (e) {} |
|
228 switch (status) { |
|
229 case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE: |
|
230 case Ci.nsIGfxInfo.FEATURE_DISCOURAGED: |
|
231 msg = ["blockedGfxCard"]; |
|
232 break; |
|
233 case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION: |
|
234 msg = ["blockedOSVersion"]; |
|
235 break; |
|
236 case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION: |
|
237 try { |
|
238 var suggestedDriverVersion = |
|
239 gfxInfo.getFeatureSuggestedDriverVersion(feature); |
|
240 } |
|
241 catch (e) {} |
|
242 msg = suggestedDriverVersion ? |
|
243 ["tryNewerDriver", suggestedDriverVersion] : |
|
244 ["blockedDriver"]; |
|
245 break; |
|
246 } |
|
247 return msg; |
|
248 } |
|
249 |
|
250 let data = {}; |
|
251 |
|
252 try { |
|
253 // nsIGfxInfo may not be implemented on some platforms. |
|
254 var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); |
|
255 } |
|
256 catch (e) {} |
|
257 |
|
258 data.numTotalWindows = 0; |
|
259 data.numAcceleratedWindows = 0; |
|
260 let winEnumer = Services.ww.getWindowEnumerator(); |
|
261 while (winEnumer.hasMoreElements()) { |
|
262 data.numTotalWindows++; |
|
263 let winUtils = winEnumer.getNext(). |
|
264 QueryInterface(Ci.nsIInterfaceRequestor). |
|
265 getInterface(Ci.nsIDOMWindowUtils); |
|
266 try { |
|
267 data.windowLayerManagerType = winUtils.layerManagerType; |
|
268 data.windowLayerManagerRemote = winUtils.layerManagerRemote; |
|
269 } |
|
270 catch (e) { |
|
271 continue; |
|
272 } |
|
273 if (data.windowLayerManagerType != "Basic") |
|
274 data.numAcceleratedWindows++; |
|
275 } |
|
276 |
|
277 if (!data.numAcceleratedWindows && gfxInfo) { |
|
278 let feature = |
|
279 #ifdef XP_WIN |
|
280 gfxInfo.FEATURE_DIRECT3D_9_LAYERS; |
|
281 #else |
|
282 gfxInfo.FEATURE_OPENGL_LAYERS; |
|
283 #endif |
|
284 data.numAcceleratedWindowsMessage = statusMsgForFeature(feature); |
|
285 } |
|
286 |
|
287 if (!gfxInfo) { |
|
288 done(data); |
|
289 return; |
|
290 } |
|
291 |
|
292 // keys are the names of attributes on nsIGfxInfo, values become the names |
|
293 // of the corresponding properties in our data object. A null value means |
|
294 // no change. This is needed so that the names of properties in the data |
|
295 // object are the same as the names of keys in aboutSupport.properties. |
|
296 let gfxInfoProps = { |
|
297 adapterDescription: null, |
|
298 adapterVendorID: null, |
|
299 adapterDeviceID: null, |
|
300 adapterRAM: null, |
|
301 adapterDriver: "adapterDrivers", |
|
302 adapterDriverVersion: "driverVersion", |
|
303 adapterDriverDate: "driverDate", |
|
304 |
|
305 adapterDescription2: null, |
|
306 adapterVendorID2: null, |
|
307 adapterDeviceID2: null, |
|
308 adapterRAM2: null, |
|
309 adapterDriver2: "adapterDrivers2", |
|
310 adapterDriverVersion2: "driverVersion2", |
|
311 adapterDriverDate2: "driverDate2", |
|
312 isGPU2Active: null, |
|
313 |
|
314 D2DEnabled: "direct2DEnabled", |
|
315 DWriteEnabled: "directWriteEnabled", |
|
316 DWriteVersion: "directWriteVersion", |
|
317 cleartypeParameters: "clearTypeParameters", |
|
318 }; |
|
319 |
|
320 for (let prop in gfxInfoProps) { |
|
321 try { |
|
322 data[gfxInfoProps[prop] || prop] = gfxInfo[prop]; |
|
323 } |
|
324 catch (e) {} |
|
325 } |
|
326 |
|
327 if (("direct2DEnabled" in data) && !data.direct2DEnabled) |
|
328 data.direct2DEnabledMessage = |
|
329 statusMsgForFeature(Ci.nsIGfxInfo.FEATURE_DIRECT2D); |
|
330 |
|
331 let doc = |
|
332 Cc["@mozilla.org/xmlextras/domparser;1"] |
|
333 .createInstance(Ci.nsIDOMParser) |
|
334 .parseFromString("<html/>", "text/html"); |
|
335 |
|
336 let canvas = doc.createElement("canvas"); |
|
337 canvas.width = 1; |
|
338 canvas.height = 1; |
|
339 |
|
340 let gl; |
|
341 try { |
|
342 gl = canvas.getContext("experimental-webgl"); |
|
343 } catch(e) {} |
|
344 |
|
345 if (gl) { |
|
346 let ext = gl.getExtension("WEBGL_debug_renderer_info"); |
|
347 // this extension is unconditionally available to chrome. No need to check. |
|
348 data.webglRenderer = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) |
|
349 + " -- " |
|
350 + gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); |
|
351 } else { |
|
352 let feature = |
|
353 #ifdef XP_WIN |
|
354 // If ANGLE is not available but OpenGL is, we want to report on the |
|
355 // OpenGL feature, because that's what's going to get used. In all |
|
356 // other cases we want to report on the ANGLE feature. |
|
357 gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE) != |
|
358 Ci.nsIGfxInfo.FEATURE_NO_INFO && |
|
359 gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL) == |
|
360 Ci.nsIGfxInfo.FEATURE_NO_INFO ? |
|
361 Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL : |
|
362 Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE; |
|
363 #else |
|
364 Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL; |
|
365 #endif |
|
366 data.webglRendererMessage = statusMsgForFeature(feature); |
|
367 } |
|
368 |
|
369 let infoInfo = gfxInfo.getInfo(); |
|
370 if (infoInfo) |
|
371 data.info = infoInfo; |
|
372 |
|
373 let failures = gfxInfo.getFailures(); |
|
374 if (failures.length) |
|
375 data.failures = failures; |
|
376 |
|
377 done(data); |
|
378 }, |
|
379 |
|
380 javaScript: function javaScript(done) { |
|
381 let data = {}; |
|
382 let winEnumer = Services.ww.getWindowEnumerator(); |
|
383 if (winEnumer.hasMoreElements()) |
|
384 data.incrementalGCEnabled = winEnumer.getNext(). |
|
385 QueryInterface(Ci.nsIInterfaceRequestor). |
|
386 getInterface(Ci.nsIDOMWindowUtils). |
|
387 isIncrementalGCEnabled(); |
|
388 done(data); |
|
389 }, |
|
390 |
|
391 accessibility: function accessibility(done) { |
|
392 let data = {}; |
|
393 try { |
|
394 data.isActive = Components.manager.QueryInterface(Ci.nsIServiceManager). |
|
395 isServiceInstantiatedByContractID( |
|
396 "@mozilla.org/accessibilityService;1", |
|
397 Ci.nsISupports); |
|
398 } |
|
399 catch (e) { |
|
400 data.isActive = false; |
|
401 } |
|
402 try { |
|
403 data.forceDisabled = |
|
404 Services.prefs.getIntPref("accessibility.force_disabled"); |
|
405 } |
|
406 catch (e) {} |
|
407 done(data); |
|
408 }, |
|
409 |
|
410 libraryVersions: function libraryVersions(done) { |
|
411 let data = {}; |
|
412 let verInfo = Cc["@mozilla.org/security/nssversion;1"]. |
|
413 getService(Ci.nsINSSVersion); |
|
414 for (let prop in verInfo) { |
|
415 let match = /^([^_]+)_((Min)?Version)$/.exec(prop); |
|
416 if (match) { |
|
417 let verProp = match[2][0].toLowerCase() + match[2].substr(1); |
|
418 data[match[1]] = data[match[1]] || {}; |
|
419 data[match[1]][verProp] = verInfo[prop]; |
|
420 } |
|
421 } |
|
422 done(data); |
|
423 }, |
|
424 |
|
425 userJS: function userJS(done) { |
|
426 let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile); |
|
427 userJSFile.append("user.js"); |
|
428 done({ |
|
429 exists: userJSFile.exists() && userJSFile.fileSize > 0, |
|
430 }); |
|
431 }, |
|
432 }; |