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