gecko-dev/toolkit/modules/Troubleshoot.jsm
Paolo Amadini 5974d2958c Bug 1362384 - Remove code to import data from "downloads.sqlite". r=mak
When upgrading an old profile that still uses "downloads.sqlite", information about in-progress and paused downloads will be lost. The history of completed downloads will be preserved because it is stored in the Places database, although it may be affected by history expiration.

MozReview-Commit-ID: GFqvACKC4E1

--HG--
extra : source : 63d49c2d1fd68be15e295b90e9f0c98170158466
2017-05-15 10:48:04 +01:00

632 lines
20 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");
Cu.import("resource://gre/modules/AppConstants.jsm");
var 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.",
"apz.",
"browser.cache.",
"browser.display.",
"browser.download.folderList",
"browser.download.hide_plugins_without_extensions",
"browser.download.lastDir.savePerSite",
"browser.download.manager.addToRecentDocs",
"browser.download.manager.alertOnEXEOpen",
"browser.download.manager.resumeOnWakeDelay",
"browser.download.preferred.",
"browser.download.useDownloadDir",
"browser.fixup.",
"browser.history_expire_",
"browser.link.open_newwindow",
"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.",
"services.sync.declinedEngines",
"services.sync.lastPing",
"services.sync.lastSync",
"services.sync.numClients",
"services.sync.engine.",
"social.enabled",
"storage.vacuum.last.",
"svg.",
"toolkit.startup.recent_crashes",
"ui.osk.enabled",
"ui.osk.detect_physical_keyboard",
"ui.osk.require_tablet_mode",
"ui.osk.debug.keyboardDisplayReason",
"webgl.",
];
// The blacklist, unlike the whitelist, is a list of regular expressions.
const PREFS_BLACKLIST = [
/^network[.]proxy[.]/,
/[.]print_to_filename$/,
/^print[.]macosx[.]pagesetup/,
];
// Table of getters for various preference types.
// It's important to use getComplexValue for strings: it returns Unicode (wchars), getCharPref returns UTF-8 encoded chars.
const PREFS_GETTERS = {};
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_STRING] = (prefs, name) => prefs.getStringPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_INT] = (prefs, name) => prefs.getIntPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_BOOL] = (prefs, name) => prefs.getBoolPref(name);
// Return the preferences filtered by PREFS_BLACKLIST and PREFS_WHITELIST lists
// and also by the custom 'filter'-ing function.
function getPrefList(filter) {
filter = filter || (name => true);
function getPref(name) {
let type = Services.prefs.getPrefType(name);
if (!(type in PREFS_GETTERS))
throw new Error("Unknown preference type " + type + " for " + name);
return PREFS_GETTERS[type](Services.prefs, name);
}
return PREFS_WHITELIST.reduce(function(prefs, branch) {
Services.prefs.getChildList(branch).forEach(function(name) {
if (filter(name) && !PREFS_BLACKLIST.some(re => re.test(name)))
prefs[name] = getPref(name);
});
return prefs;
}, {});
}
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.dispatchToMainThread(done.bind(null, snapshot));
}
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.
var dataProviders = {
application: function application(done) {
let sysInfo = Cc["@mozilla.org/system-info;1"].
getService(Ci.nsIPropertyBag2);
let data = {
name: Services.appinfo.name,
osVersion: sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"),
version: AppConstants.MOZ_APP_VERSION_DISPLAY,
buildID: Services.appinfo.appBuildID,
userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].
getService(Ci.nsIHttpProtocolHandler).
userAgent,
safeMode: Services.appinfo.inSafeMode,
};
if (AppConstants.MOZ_UPDATER)
data.updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
// eslint-disable-next-line mozilla/use-default-preference-values
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.wm.getEnumerator("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;
try {
let e10sStatus = Cc["@mozilla.org/supports-PRUint64;1"]
.createInstance(Ci.nsISupportsPRUint64);
let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver);
appinfo.observe(e10sStatus, "getE10SBlocked", "");
data.autoStartStatus = e10sStatus.data;
} catch (e) {
data.autoStartStatus = -1;
}
const keyGoogle = Services.urlFormatter.formatURL("%GOOGLE_API_KEY%").trim();
data.keyGoogleFound = keyGoogle != "no-google-api-key" && keyGoogle.length > 0;
const keyMozilla = Services.urlFormatter.formatURL("%MOZILLA_API_KEY%").trim();
data.keyMozillaFound = keyMozilla != "no-mozilla-api-key" && keyMozilla.length > 0;
done(data);
},
extensions: function extensions(done) {
AddonManager.getAddonsByTypes(["extension"], function(extensions) {
extensions = extensions.filter(e => !e.isSystem);
extensions.sort(function(a, b) {
if (a.isActive != b.isActive)
return b.isActive ? 1 : -1;
// In some unfortunate cases addon names can be null.
let aname = a.name || null;
let bname = b.name || null;
let lc = aname.localeCompare(bname);
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;
}, {});
}));
});
},
features: function features(done) {
AddonManager.getAddonsByTypes(["extension"], function(features) {
features = features.filter(f => f.isSystem);
features.sort(function(a, b) {
// In some unfortunate cases addon names can be null.
let aname = a.name || null;
let bname = b.name || null;
let lc = aname.localeCompare(bname);
if (lc != 0)
return lc;
if (a.version != b.version)
return a.version > b.version ? 1 : -1;
return 0;
});
let props = ["name", "version", "id"];
done(features.map(function(f) {
return props.reduce(function(fData, prop) {
fData[prop] = f[prop];
return fData;
}, {});
}));
});
},
experiments: function experiments(done) {
if (Experiments === undefined) {
done([]);
return;
}
// getExperiments promises experiment history
Experiments.instance().getExperiments().then(
experiments => done(experiments)
);
},
modifiedPreferences: function modifiedPreferences(done) {
done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
},
lockedPreferences: function lockedPreferences(done) {
done(getPrefList(name => Services.prefs.prefIsLocked(name)));
},
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;
case Ci.nsIGfxInfo.FEATURE_BLOCKED_MISMATCHED_VERSION:
msg = ["blockedMismatchedVersion"];
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) {}
let promises = [];
// done will be called upon all pending promises being resolved.
// add your pending promise to promises when adding new ones.
function completed() {
Promise.all(promises).then(() => done(data));
}
data.numTotalWindows = 0;
data.numAcceleratedWindows = 0;
let winEnumer = Services.ww.getWindowEnumerator();
while (winEnumer.hasMoreElements()) {
let winUtils = winEnumer.getNext().
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
try {
// NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
if (winUtils.layerManagerType == "None" || !winUtils.layerManagerRemote) {
continue;
}
data.numTotalWindows++;
data.windowLayerManagerType = winUtils.layerManagerType;
data.windowLayerManagerRemote = winUtils.layerManagerRemote;
} catch (e) {
continue;
}
if (data.windowLayerManagerType != "Basic")
data.numAcceleratedWindows++;
}
// If we had no OMTC windows, report back Basic Layers.
if (!data.windowLayerManagerType) {
data.windowLayerManagerType = "Basic";
data.windowLayerManagerRemote = false;
}
let winUtils = Services.wm.getMostRecentWindow("").
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils)
data.currentAudioBackend = winUtils.currentAudioBackend;
if (!data.numAcceleratedWindows && gfxInfo) {
let win = AppConstants.platform == "win";
let feature = win ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS :
gfxInfo.FEATURE_OPENGL_LAYERS;
data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
}
if (!gfxInfo) {
completed();
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");
function GetWebGLInfo(data, keyPrefix, contextType) {
data[keyPrefix + "Renderer"] = "-";
data[keyPrefix + "Version"] = "-";
data[keyPrefix + "DriverExtensions"] = "-";
data[keyPrefix + "Extensions"] = "-";
data[keyPrefix + "WSIInfo"] = "-";
// //
let canvas = doc.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
// //
let creationError = null;
canvas.addEventListener(
"webglcontextcreationerror",
function(e) {
creationError = e.statusMessage;
}
);
let gl = null;
try {
gl = canvas.getContext(contextType);
} catch (e) {
if (!creationError) {
creationError = e.toString();
}
}
if (!gl) {
data[keyPrefix + "Renderer"] = creationError || "(no creation error info)";
return;
}
// //
data[keyPrefix + "Extensions"] = gl.getSupportedExtensions().join(" ");
// //
let ext = gl.getExtension("MOZ_debug");
// This extension is unconditionally available to chrome. No need to check.
let vendor = ext.getParameter(gl.VENDOR);
let renderer = ext.getParameter(gl.RENDERER);
data[keyPrefix + "Renderer"] = vendor + " -- " + renderer;
data[keyPrefix + "Version"] = ext.getParameter(gl.VERSION);
data[keyPrefix + "DriverExtensions"] = ext.getParameter(ext.EXTENSIONS);
data[keyPrefix + "WSIInfo"] = ext.getParameter(ext.WSI_INFO);
// //
// Eagerly free resources.
let loseExt = gl.getExtension("WEBGL_lose_context");
loseExt.loseContext();
}
GetWebGLInfo(data, "webgl1", "webgl");
GetWebGLInfo(data, "webgl2", "webgl2");
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;
}
}
data.featureLog = gfxInfo.getFeatureLog();
data.crashGuards = gfxInfo.getActiveCrashGuards();
completed();
},
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 = {};
data.isActive = Cc["@mozilla.org/xre/app-info;1"].
getService(Ci.nsIXULRuntime).
accessibilityEnabled;
// eslint-disable-next-line mozilla/use-default-preference-values
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 (AppConstants.MOZ_CRASHREPORTER) {
dataProviders.crashes = function crashes(done) {
let CrashReports = Cu.import("resource://gre/modules/CrashReports.jsm").CrashReports;
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);
}
}
if (AppConstants.MOZ_SANDBOX) {
dataProviders.sandbox = function sandbox(done) {
let data = {};
if (AppConstants.platform == "linux") {
const keys = ["hasSeccompBPF", "hasSeccompTSync",
"hasPrivilegedUserNamespaces", "hasUserNamespaces",
"canSandboxContent", "canSandboxMedia"];
let sysInfo = Cc["@mozilla.org/system-info;1"].
getService(Ci.nsIPropertyBag2);
for (let key of keys) {
if (sysInfo.hasKey(key)) {
data[key] = sysInfo.getPropertyAsBool(key);
}
}
let reporter = Cc["@mozilla.org/sandbox/syscall-reporter;1"].
getService(Ci.mozISandboxReporter);
const snapshot = reporter.snapshot();
let syscalls = [];
for (let index = snapshot.begin; index < snapshot.end; ++index) {
let report = snapshot.getElement(index);
let { msecAgo, pid, tid, procType, syscall } = report;
let args = []
for (let i = 0; i < report.numArgs; ++i) {
args.push(report.getArg(i));
}
syscalls.push({ index, msecAgo, pid, tid, procType, syscall, args });
}
data.syscallLog = syscalls;
}
if (AppConstants.MOZ_CONTENT_SANDBOX) {
data.contentSandboxLevel =
Services.prefs.getIntPref("security.sandbox.content.level");
}
done(data);
}
}