diff options
Diffstat (limited to 'toolkit/modules/Troubleshoot.sys.mjs')
-rw-r--r-- | toolkit/modules/Troubleshoot.sys.mjs | 1130 |
1 files changed, 1130 insertions, 0 deletions
diff --git a/toolkit/modules/Troubleshoot.sys.mjs b/toolkit/modules/Troubleshoot.sys.mjs new file mode 100644 index 0000000000..53259bcb67 --- /dev/null +++ b/toolkit/modules/Troubleshoot.sys.mjs @@ -0,0 +1,1130 @@ +/* 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/. */ + +import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs"; +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + +import { FeatureGate } from "resource://featuregates/FeatureGate.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.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.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 = Services.sysinfo + .getProperty("pointingDevices") + .split(","); + } catch (e) {} + + data.numTotalWindows = 0; + data.numFissionWindows = 0; + data.numRemoteWindows = 0; + for (let { docShell } of Services.wm.getEnumerator("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; + // Workaround for bug 1790070, since requestProcInfo refers to the preallocated content + // process as "preallocated", and the localization string mapping expects "prealloc". + remoteType = remoteType === "preallocated" ? "prealloc" : remoteType; + } catch (e) {} + + // 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; + } + } + + 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.platform == "android") { + done(); + return; + } + 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.getFeatureStatus(feature); + } catch (e) {} + switch (status) { + case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE: + case Ci.nsIGfxInfo.FEATURE_DISCOURAGED: + msg = { key: "blocked-gfx-card" }; + break; + case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION: + msg = { key: "blocked-os-version" }; + break; + case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION: + try { + var driverVersion = + gfxInfo.getFeatureSuggestedDriverVersion(feature); + } catch (e) {} + msg = driverVersion + ? { key: "try-newer-driver", args: { driverVersion } } + : { key: "blocked-driver" }; + break; + case Ci.nsIGfxInfo.FEATURE_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 + ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS + : gfxInfo.FEATURE_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( + Ci.nsIGfxInfo.FEATURE_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 = await adapter.requestAdapterInfo(); + // 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[`requestAdapterInfo()`] = 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, + }, + }); + }, + + 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, + }); + }, +}; + +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 }); + }; +} |