Source code
Revision control
Copy as Markdown
Other Tools
/* 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
import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
});
// We use a list of prefs for display to make sure we only show prefs 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_FOR_DISPLAY = [
"accessibility.",
"apz.",
"browser.cache.",
"browser.contentblocking.category",
"browser.contentanalysis.",
"browser.display.",
"browser.download.always_ask_before_handling_new_types",
"browser.download.enable_spam_prevention",
"browser.download.folderList",
"browser.download.improvements_to_download_panel",
"browser.download.lastDir.savePerSite",
"browser.download.manager.addToRecentDocs",
"browser.download.manager.resumeOnWakeDelay",
"browser.download.open_pdf_attachments_inline",
"browser.download.preferred.",
"browser.download.skipConfirmLaunchExecutable",
"browser.download.start_downloads_in_tmp_dir",
"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.region",
"browser.search.searchEnginesURL",
"browser.search.suggest.enabled",
"browser.search.update",
"browser.sessionstore.",
"browser.startup.homepage",
"browser.startup.page",
"browser.tabs.",
"browser.toolbars.",
"browser.urlbar.",
"browser.zoom.",
"doh-rollout.",
"dom.",
"extensions.checkCompatibility",
"extensions.eventPages.enabled",
"extensions.formautofill.",
"extensions.lastAppVersion",
"extensions.manifestV3.enabled",
"extensions.quarantinedDomains.enabled",
"extensions.InstallTrigger.enabled",
"extensions.InstallTriggerImpl.enabled",
"fission.autostart",
"font.",
"general.autoScroll",
"general.useragent.",
"gfx.",
"html5.",
"identity.fxaccounts.enabled",
"idle.",
"image.",
"javascript.",
"keyword.",
"layers.",
"layout.css.dpi",
"layout.display-list.",
"layout.frame_rate",
"media.",
"mousewheel.",
"network.",
"permissions.default.image",
"places.",
"plugin.",
"plugins.",
"privacy.",
"security.",
"services.sync.declinedEngines",
"services.sync.lastPing",
"services.sync.lastSync",
"services.sync.numClients",
"services.sync.engine.",
"signon.",
"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.",
"widget.dmabuf",
"widget.use-xdg-desktop-portal",
"widget.use-xdg-desktop-portal.file-picker",
"widget.use-xdg-desktop-portal.mime-handler",
"widget.gtk.overlay-scrollbars.enabled",
"widget.wayland",
];
// The list of prefs we don't display, unlike the list of prefs for display,
// is a list of regular expressions.
const PREF_REGEXES_NOT_TO_DISPLAY = [
/^browser[.]fixup[.]domainwhitelist[.]/,
/^dom[.]push[.]userAgentID/,
/^media[.]webrtc[.]debug[.]aec_log_dir/,
/^media[.]webrtc[.]debug[.]log_file/,
/^print[.].*print_to_filename$/,
/^network[.]proxy[.]/,
];
// Table of getters for various preference types.
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);
// List of unimportant locked prefs (won't be shown on the troubleshooting
// session)
const PREFS_UNIMPORTANT_LOCKED = [
"dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled",
"extensions.backgroundServiceWorkerEnabled.enabled",
"privacy.restrict3rdpartystorage.url_decorations",
];
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 the preferences filtered by PREF_REGEXES_NOT_TO_DISPLAY and PREFS_FOR_DISPLAY
// and also by the custom 'filter'-ing function.
function getPrefList(filter, allowlist = PREFS_FOR_DISPLAY) {
return allowlist.reduce(function (prefs, branch) {
Services.prefs.getChildList(branch).forEach(function (name) {
if (
filter(name) &&
!PREF_REGEXES_NOT_TO_DISPLAY.some(re => re.test(name))
) {
prefs[name] = getPref(name);
}
});
return prefs;
}, {});
}
export var Troubleshoot = {
/**
* Captures a snapshot of data that may help troubleshooters troubleshoot
* trouble.
*
* @returns {Promise}
* A promise that is resolved with the snapshot data.
*/
snapshot() {
return new Promise(resolve => {
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(() => resolve(snapshot));
}
}
for (let name in dataProviders) {
try {
dataProviders[name](providerDone.bind(null, name));
} catch (err) {
let msg = "Troubleshoot data provider failed: " + name + "\n" + err;
console.error(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: async function application(done) {
let data = {
name: Services.appinfo.name,
osVersion:
Services.sysinfo.getProperty("name") +
" " +
Services.sysinfo.getProperty("version") +
" " +
Services.sysinfo.getProperty("build"),
version: AppConstants.MOZ_APP_VERSION_DISPLAY,
buildID: Services.appinfo.appBuildID,
distributionID: Services.prefs
.getDefaultBranch("")
.getCharPref("distribution.id", ""),
userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].getService(
Ci.nsIHttpProtocolHandler
).userAgent,
safeMode: Services.appinfo.inSafeMode,
memorySizeBytes: Services.sysinfo.getProperty("memsize"),
diskAvailableBytes: Services.dirsvc.get("ProfD", Ci.nsIFile)
.diskSpaceAvailable,
};
if (Services.sysinfo.getProperty("name") == "Windows_NT") {
if ((await Services.sysinfo.processInfo).isWindowsSMode) {
data.osVersion += " S";
}
}
if (AppConstants.MOZ_UPDATER) {
data.updateChannel = ChromeUtils.importESModule(
"resource://gre/modules/UpdateUtils.sys.mjs"
).UpdateUtils.UpdateChannel;
}
// eslint-disable-next-line mozilla/use-default-preference-values
try {
data.vendor = Services.prefs.getCharPref("app.support.vendor");
} catch (e) {}
try {
data.supportURL = Services.urlFormatter.formatURLPref(
"app.support.baseURL"
);
} catch (e) {}
data.osTheme = Services.sysinfo.getProperty("osThemeInfo");
try {
// MacOSX: Check for rosetta status, if it exists
data.rosetta = Services.sysinfo.getProperty("rosettaStatus");
} catch (e) {}
try {
// Windows - Get info about attached pointing devices
data.pointingDevices = [];
if (Services.sysinfo.getProperty("hasMouse")) {
data.pointingDevices.push("pointing-device-mouse");
}
if (Services.sysinfo.getProperty("hasTouch")) {
data.pointingDevices.push("pointing-device-touchscreen");
}
if (Services.sysinfo.getProperty("hasPen")) {
data.pointingDevices.push("pointing-device-pen-digitizer");
}
if (!data.pointingDevices.length) {
data.pointingDevices.push("pointing-device-none");
}
} catch (e) {}
data.numTotalWindows = 0;
data.numFissionWindows = 0;
data.numRemoteWindows = 0;
for (let { docShell } of Services.wm.getEnumerator(
AppConstants.platform == "android"
? "navigator:geckoview"
: "navigator:browser"
)) {
docShell.QueryInterface(Ci.nsILoadContext);
data.numTotalWindows++;
if (docShell.useRemoteSubframes) {
data.numFissionWindows++;
}
if (docShell.useRemoteTabs) {
data.numRemoteWindows++;
}
}
try {
data.launcherProcessState = Services.appinfo.launcherProcessState;
} catch (e) {}
data.fissionAutoStart = Services.appinfo.fissionAutostart;
data.fissionDecisionStatus = Services.appinfo.fissionDecisionStatusString;
data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart;
if (Services.policies) {
data.policiesStatus = Services.policies.status;
}
const keyLocationServiceGoogle = Services.urlFormatter
.formatURL("%GOOGLE_LOCATION_SERVICE_API_KEY%")
.trim();
data.keyLocationServiceGoogleFound =
keyLocationServiceGoogle != "no-google-location-service-api-key" &&
!!keyLocationServiceGoogle.length;
const keySafebrowsingGoogle = Services.urlFormatter
.formatURL("%GOOGLE_SAFEBROWSING_API_KEY%")
.trim();
data.keySafebrowsingGoogleFound =
keySafebrowsingGoogle != "no-google-safebrowsing-api-key" &&
!!keySafebrowsingGoogle.length;
const keyMozilla = Services.urlFormatter
.formatURL("%MOZILLA_API_KEY%")
.trim();
data.keyMozillaFound =
keyMozilla != "no-mozilla-api-key" && !!keyMozilla.length;
done(data);
},
addons: async function addons(done) {
let addons = await AddonManager.getAddonsByTypes([
"extension",
"locale",
"dictionary",
"sitepermission",
"theme",
]);
addons = addons.filter(e => !e.isSystem);
addons.sort(function (a, b) {
if (a.isActive != b.isActive) {
return b.isActive ? 1 : -1;
}
if (a.type != b.type) {
return a.type.localeCompare(b.type);
}
// In some unfortunate cases add-on names can be null.
let aname = a.name || "";
let bname = b.name || "";
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", "type", "version", "isActive", "id"];
done(
addons.map(function (ext) {
return props.reduce(function (extData, prop) {
extData[prop] = ext[prop];
return extData;
}, {});
})
);
},
securitySoftware: function securitySoftware(done) {
let data = {};
const keys = [
"registeredAntiVirus",
"registeredAntiSpyware",
"registeredFirewall",
];
for (let key of keys) {
let prop = "";
try {
prop = Services.sysinfo.getProperty(key);
} catch (e) {}
data[key] = prop;
}
done(data);
},
features: async function features(done) {
let features = await AddonManager.getAddonsByTypes(["extension"]);
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;
}, {});
})
);
},
processes: async function processes(done) {
let remoteTypes = {};
const processInfo = await ChromeUtils.requestProcInfo();
for (let i = 0; i < processInfo.children.length; i++) {
let remoteType;
try {
remoteType = processInfo.children[i].type;
// process as "preallocated", and the localization string mapping expects "prealloc".
remoteType = remoteType === "preallocated" ? "prealloc" : remoteType;
} catch (e) {}
// We will split Utility by actor name, so do not do it now
if (remoteType === "utility") {
continue;
}
// The parent process is also managed by the ppmm (because
// of non-remote tabs), but it doesn't have a remoteType.
if (!remoteType) {
continue;
}
if (remoteTypes[remoteType]) {
remoteTypes[remoteType]++;
} else {
remoteTypes[remoteType] = 1;
}
}
for (let i = 0; i < processInfo.children.length; i++) {
if (processInfo.children[i].type === "utility") {
for (let utilityWithActor of processInfo.children[i].utilityActors.map(
e => `utility_${e.actorName}`
)) {
if (remoteTypes[utilityWithActor]) {
remoteTypes[utilityWithActor]++;
} else {
remoteTypes[utilityWithActor] = 1;
}
}
}
}
try {
let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
if (winUtils.gpuProcessPid != -1) {
remoteTypes.gpu = 1;
}
} catch (e) {}
if (Services.io.socketProcessLaunched) {
remoteTypes.socket = 1;
}
let data = {
remoteTypes,
maxWebContentProcesses: Services.appinfo.maxWebProcessCount,
};
done(data);
},
async experimentalFeatures(done) {
if (AppConstants.MOZ_BUILD_APP != "browser") {
done();
return;
}
let { FeatureGate } = ChromeUtils.importESModule(
"resource://featuregates/FeatureGate.sys.mjs"
);
let gates = await FeatureGate.all();
done(
gates.map(gate => {
return [
gate.title,
gate.preference,
Services.prefs.getBoolPref(gate.preference),
];
})
);
},
async legacyUserStylesheets(done) {
if (AppConstants.platform == "android") {
done({ active: false, types: [] });
return;
}
let active = Services.prefs.getBoolPref(
"toolkit.legacyUserProfileCustomizations.stylesheets"
);
let types = [];
for (let name of ["userChrome.css", "userContent.css"]) {
let path = PathUtils.join(PathUtils.profileDir, "chrome", name);
if (await IOUtils.exists(path)) {
types.push(name);
}
}
done({ active, types });
},
async environmentVariables(done) {
let Subprocess;
try {
// Subprocess is not available in all builds
Subprocess = ChromeUtils.importESModule(
"resource://gre/modules/Subprocess.sys.mjs"
).Subprocess;
} catch (ex) {
done({});
return;
}
let environment = Subprocess.getEnvironment();
let filteredEnvironment = {};
// Limit the environment variables to those that we
// know may affect Firefox to reduce leaking PII.
let filteredEnvironmentKeys = ["xre_", "moz_", "gdk", "display"];
for (let key of Object.keys(environment)) {
if (filteredEnvironmentKeys.some(k => key.toLowerCase().startsWith(k))) {
filteredEnvironment[key] = environment[key];
}
}
done(filteredEnvironment);
},
modifiedPreferences: function modifiedPreferences(done) {
done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
},
lockedPreferences: function lockedPreferences(done) {
done(
getPrefList(
name =>
!PREFS_UNIMPORTANT_LOCKED.includes(name) &&
Services.prefs.prefIsLocked(name)
)
);
},
places: async function places(done) {
const data = AppConstants.MOZ_PLACES
? await lazy.PlacesDBUtils.getEntitiesStatsAndCounts()
: [];
done(data);
},
printingPreferences: function printingPreferences(done) {
let filter = name => Services.prefs.prefHasUserValue(name);
let prefs = getPrefList(filter, ["print."]);
// print_printer is special and is the only pref that is outside of the
// "print." branch... Maybe we should change it to print.printer or
// something...
if (filter("print_printer")) {
prefs.print_printer = getPref("print_printer");
}
done(prefs);
},
graphics: function graphics(done) {
function statusMsgForFeature(feature) {
// We return an object because in the try-newer-driver 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 object in others, return an object always.
let msg = { key: "" };
try {
var status = gfxInfo.getFeatureStatusStr(feature);
} catch (e) {}
switch (status) {
case "BLOCKED_DEVICE":
case "DISCOURAGED":
msg = { key: "blocked-gfx-card" };
break;
case "BLOCKED_OS_VERSION":
msg = { key: "blocked-os-version" };
break;
case "BLOCKED_DRIVER_VERSION":
try {
var driverVersion =
gfxInfo.getFeatureSuggestedDriverVersionStr(feature);
} catch (e) {}
msg = driverVersion
? { key: "try-newer-driver", args: { driverVersion } }
: { key: "blocked-driver" };
break;
case "BLOCKED_MISMATCHED_VERSION":
msg = { key: "blocked-mismatched-version" };
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.desktopEnvironment = Services.appinfo.desktopEnvironment;
data.numTotalWindows = 0;
data.numAcceleratedWindows = 0;
let devicePixelRatios = [];
for (let win of Services.ww.getWindowEnumerator()) {
let winUtils = win.windowUtils;
try {
// NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
if (
winUtils.layerManagerType == "None" ||
!winUtils.layerManagerRemote
) {
continue;
}
devicePixelRatios.push(win.devicePixelRatio);
data.numTotalWindows++;
data.windowLayerManagerType = winUtils.layerManagerType;
data.windowLayerManagerRemote = winUtils.layerManagerRemote;
} catch (e) {
continue;
}
if (data.windowLayerManagerType != "Basic") {
data.numAcceleratedWindows++;
}
}
data.graphicsDevicePixelRatios = devicePixelRatios;
// If we had no OMTC windows, report back Basic Layers.
if (!data.windowLayerManagerType) {
data.windowLayerManagerType = "Basic";
data.windowLayerManagerRemote = false;
}
if (!data.numAcceleratedWindows && gfxInfo) {
let win = AppConstants.platform == "win";
let feature = win ? "DIRECT3D_9_LAYERS" : "OPENGL_LAYERS";
data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
}
if (gfxInfo) {
// 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",
adapterDriverVendor: "driverVendor",
adapterDriverVersion: "driverVersion",
adapterDriverDate: "driverDate",
adapterDescription2: null,
adapterVendorID2: null,
adapterDeviceID2: null,
adapterSubsysID2: null,
adapterRAM2: null,
adapterDriver2: "adapterDrivers2",
adapterDriverVendor2: "driverVendor2",
adapterDriverVersion2: "driverVersion2",
adapterDriverDate2: "driverDate2",
isGPU2Active: null,
D2DEnabled: "direct2DEnabled",
DWriteEnabled: "directWriteEnabled",
DWriteVersion: "directWriteVersion",
cleartypeParameters: "clearTypeParameters",
TargetFrameRate: "targetFrameRate",
windowProtocol: null,
fontVisibilityDeterminationStr: "supportFontDetermination",
};
for (let prop in gfxInfoProps) {
try {
data[gfxInfoProps[prop] || prop] = gfxInfo[prop];
} catch (e) {}
}
if ("direct2DEnabled" in data && !data.direct2DEnabled) {
data.direct2DEnabledMessage = statusMsgForFeature("DIRECT2D");
}
}
let doc = new DOMParser().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");
if (loseExt) {
loseExt.loseContext();
}
}
GetWebGLInfo(data, "webgl1", "webgl");
GetWebGLInfo(data, "webgl2", "webgl2");
if (gfxInfo) {
let infoInfo = gfxInfo.getInfo();
if (infoInfo) {
data.info = infoInfo;
}
let failureIndices = {};
let failures = gfxInfo.getFailures(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();
}
function getNavigator() {
for (let win of Services.ww.getWindowEnumerator()) {
let winUtils = win.windowUtils;
try {
// NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
if (
winUtils.layerManagerType == "None" ||
!winUtils.layerManagerRemote
) {
continue;
}
const nav = win.navigator;
if (nav) {
return nav;
}
} catch (e) {
continue;
}
}
throw new Error("No window had window.navigator.");
}
const navigator = getNavigator();
async function GetWebgpuInfo(adapterOpts) {
const ret = {};
if (!navigator.gpu) {
ret["navigator.gpu"] = null;
return ret;
}
const requestAdapterkey = `navigator.gpu.requestAdapter(${JSON.stringify(
adapterOpts
)})`;
let adapter;
try {
adapter = await navigator.gpu.requestAdapter(adapterOpts);
} catch (e) {
// If WebGPU isn't supported or is blocked somehow, include
// that in the report. Anything else is an error which should
// have consequences (test failures, etc).
if (DOMException.isInstance(e) && e.name == "NotSupportedError") {
return { [requestAdapterkey]: { not_supported: e.message } };
}
throw e;
}
if (!adapter) {
ret[requestAdapterkey] = null;
return ret;
}
const desc = (ret[requestAdapterkey] = {});
desc.isFallbackAdapter = adapter.isFallbackAdapter;
const adapterInfo = adapter.info;
// We can't directly enumerate properties of instances of `GPUAdapterInfo`s, so use the prototype instead.
const adapterInfoObj = {};
for (const k of Object.keys(Object.getPrototypeOf(adapterInfo)).sort()) {
adapterInfoObj[k] = adapterInfo[k];
}
desc.info = adapterInfoObj;
desc.features = Array.from(adapter.features).sort();
desc.limits = {};
const keys = Object.keys(Object.getPrototypeOf(adapter.limits)).sort(); // limits not directly enumerable?
for (const k of keys) {
desc.limits[k] = adapter.limits[k];
}
return ret;
}
// Webgpu info is going to need awaits.
(async () => {
data.webgpuDefaultAdapter = await GetWebgpuInfo({});
data.webgpuFallbackAdapter = await GetWebgpuInfo({
forceFallbackAdapter: true,
});
done(data);
})();
},
media: function media(done) {
function convertDevices(devices) {
if (!devices) {
return undefined;
}
let infos = [];
for (let i = 0; i < devices.length; ++i) {
let device = devices.queryElementAt(i, Ci.nsIAudioDeviceInfo);
infos.push({
name: device.name,
groupId: device.groupId,
vendor: device.vendor,
type: device.type,
state: device.state,
preferred: device.preferred,
supportedFormat: device.supportedFormat,
defaultFormat: device.defaultFormat,
maxChannels: device.maxChannels,
defaultRate: device.defaultRate,
maxRate: device.maxRate,
minRate: device.minRate,
maxLatency: device.maxLatency,
minLatency: device.minLatency,
});
}
return infos;
}
let data = {};
let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
data.currentAudioBackend = winUtils.currentAudioBackend;
data.currentMaxAudioChannels = winUtils.currentMaxAudioChannels;
data.currentPreferredSampleRate = winUtils.currentPreferredSampleRate;
data.audioOutputDevices = convertDevices(
winUtils
.audioDevices(Ci.nsIDOMWindowUtils.AUDIO_OUTPUT)
.QueryInterface(Ci.nsIArray)
);
data.audioInputDevices = convertDevices(
winUtils
.audioDevices(Ci.nsIDOMWindowUtils.AUDIO_INPUT)
.QueryInterface(Ci.nsIArray)
);
data.codecSupportInfo = "Unknown";
// We initialize gfxInfo here in the same way as in the media
// section -- should we break this out into a separate function?
try {
// nsIGfxInfo may not be implemented on some platforms.
var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
// Note: CodecSupportInfo is not populated until we have
// actually instantiated a PDM. We may want to add a button
// or some other means of allowing the user to manually
// instantiate a PDM to ensure that the data is available.
data.codecSupportInfo = gfxInfo.CodecSupportInfo;
} catch (e) {}
done(data);
},
accessibility: function accessibility(done) {
let data = {};
data.isActive = Services.appinfo.accessibilityEnabled;
// eslint-disable-next-line mozilla/use-default-preference-values
try {
data.forceDisabled = Services.prefs.getIntPref(
"accessibility.force_disabled"
);
} catch (e) {}
data.instantiator = Services.appinfo.accessibilityInstantiator;
done(data);
},
startupCache: function startupCache(done) {
const startupInfo = Cc["@mozilla.org/startupcacheinfo;1"].getService(
Ci.nsIStartupCacheInfo
);
done({
DiskCachePath: startupInfo.DiskCachePath,
IgnoreDiskCache: startupInfo.IgnoreDiskCache,
FoundDiskCacheOnInit: startupInfo.FoundDiskCacheOnInit,
WroteToDiskCache: startupInfo.WroteToDiskCache,
});
},
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,
});
},
intl: function intl(done) {
const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
Ci.mozIOSPreferences
);
done({
localeService: {
requested: Services.locale.requestedLocales,
available: Services.locale.availableLocales,
supported: Services.locale.appLocalesAsBCP47,
regionalPrefs: Services.locale.regionalPrefsLocales,
defaultLocale: Services.locale.defaultLocale,
},
osPrefs: {
systemLocales: osPrefs.systemLocales,
regionalPrefsLocales: osPrefs.regionalPrefsLocales,
},
});
},
contentAnalysis: async function contentAnalysis(done) {
const contentAnalysis = Cc["@mozilla.org/contentanalysis;1"].getService(
Ci.nsIContentAnalysis
);
if (!contentAnalysis.isActive) {
done({ active: false });
return;
}
let info = await contentAnalysis.getDiagnosticInfo();
done({
active: true,
connected: info.connectedToAgent,
agentPath: info.agentPath,
failedSignatureVerification: info.failedSignatureVerification,
requestCount: info.requestCount,
});
},
async normandy(done) {
if (!AppConstants.MOZ_NORMANDY) {
done();
return;
}
const { PreferenceExperiments: NormandyPreferenceStudies } =
ChromeUtils.importESModule(
"resource://normandy/lib/PreferenceExperiments.sys.mjs"
);
const { AddonStudies: NormandyAddonStudies } = ChromeUtils.importESModule(
"resource://normandy/lib/AddonStudies.sys.mjs"
);
const { PreferenceRollouts: NormandyPreferenceRollouts } =
ChromeUtils.importESModule(
"resource://normandy/lib/PreferenceRollouts.sys.mjs"
);
const { ExperimentManager } = ChromeUtils.importESModule(
"resource://nimbus/lib/ExperimentManager.sys.mjs"
);
// Get Normandy data in parallel, and sort each group by slug.
const [
addonStudies,
prefRollouts,
prefStudies,
nimbusExperiments,
nimbusRollouts,
] = await Promise.all(
[
NormandyAddonStudies.getAllActive(),
NormandyPreferenceRollouts.getAllActive(),
NormandyPreferenceStudies.getAllActive(),
ExperimentManager.store
.ready()
.then(() => ExperimentManager.store.getAllActiveExperiments()),
ExperimentManager.store
.ready()
.then(() => ExperimentManager.store.getAllActiveRollouts()),
].map(promise =>
promise
.catch(error => {
console.error(error);
return [];
})
.then(items => items.sort((a, b) => a.slug.localeCompare(b.slug)))
)
);
done({
addonStudies,
prefRollouts,
prefStudies,
nimbusExperiments,
nimbusRollouts,
});
},
async remoteSettings(done) {
const { RemoteSettings } = ChromeUtils.importESModule(
);
let inspected;
try {
inspected = await RemoteSettings.inspect({ localOnly: true });
} catch (error) {
console.error(error);
done({ isSynchronizationBroken: true, history: { "settings-sync": [] } });
return;
}
// Show last check in standard format.
inspected.lastCheck = inspected.lastCheck
? new Date(inspected.lastCheck * 1000).toISOString()
: "";
// Trim history entries.
for (let h of Object.values(inspected.history)) {
h.splice(10, Infinity);
}
done(inspected);
},
};
if (AppConstants.MOZ_CRASHREPORTER) {
dataProviders.crashes = function crashes(done) {
const { CrashReports } = ChromeUtils.importESModule(
"resource://gre/modules/CrashReports.sys.mjs"
);
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.unixstyle == "linux") {
const keys = [
"hasSeccompBPF",
"hasSeccompTSync",
"hasPrivilegedUserNamespaces",
"hasUserNamespaces",
"canSandboxContent",
"canSandboxMedia",
];
for (let key of keys) {
if (Services.sysinfo.hasKey(key)) {
data[key] = Services.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_SANDBOX) {
let sandboxSettings = Cc[
"@mozilla.org/sandbox/sandbox-settings;1"
].getService(Ci.mozISandboxSettings);
data.contentSandboxLevel = Services.prefs.getIntPref(
"security.sandbox.content.level"
);
data.effectiveContentSandboxLevel =
sandboxSettings.effectiveContentSandboxLevel;
if (AppConstants.platform == "win") {
data.contentWin32kLockdownState =
sandboxSettings.contentWin32kLockdownStateString;
data.supportSandboxGpuLevel = Services.prefs.getIntPref(
"security.sandbox.gpu.level"
);
}
}
done(data);
};
}
if (AppConstants.ENABLE_WEBDRIVER) {
dataProviders.remoteAgent = function remoteAgent(done) {
const { RemoteAgent } = ChromeUtils.importESModule(
"chrome://remote/content/components/RemoteAgent.sys.mjs"
);
const { running, scheme, host, port } = RemoteAgent;
let url = "";
if (running) {
url = `${scheme}://${host}:${port}/`;
}
done({ running, url });
};
}