in mail/components/about-support/content/aboutSupport.js [485:892]
async graphics(data) {
function localizedMsg(msg) {
if (typeof msg == "object" && msg.key) {
return document.l10n.formatValue(msg.key, msg.args);
}
const msgId = toFluentID(msg);
if (msgId) {
return document.l10n.formatValue(msgId);
}
return "";
}
// Read APZ info out of data.info, stripping it out in the process.
let apzInfo = [];
const formatApzInfo = function (info) {
const out = [];
for (const type of [
"Wheel",
"Touch",
"Drag",
"Keyboard",
"Autoscroll",
"Zooming",
]) {
const key = "Apz" + type + "Input";
if (!(key in info)) {
continue;
}
delete info[key];
out.push(toFluentID(type.toLowerCase() + "Enabled"));
}
return out;
};
// Create a <tr> element with key and value columns.
//
// @key Text in the key column. Localized automatically, unless starts with "#".
// @value Fluent ID for text in the value column, or array of children.
function buildRow(key, value) {
const title = key[0] == "#" ? key.substr(1) : key;
const keyStrId = toFluentID(key);
const valueStrId = Array.isArray(value) ? null : toFluentID(value);
const td = $.new("td", value);
td.style["white-space"] = "pre-wrap";
if (valueStrId) {
document.l10n.setAttributes(td, valueStrId);
}
const th = $.new("th", title, "column");
if (!key.startsWith("#")) {
document.l10n.setAttributes(th, keyStrId);
}
return $.new("tr", [th, td]);
}
// @where The name in "graphics-<name>-tbody", of the element to append to.
// @trs Array of row elements.
function addRows(where, trs) {
$.append($("graphics-" + where + "-tbody"), trs);
}
// Build and append a row.
//
// @where The name in "graphics-<name>-tbody", of the element to append to.
function addRow(where, key, value) {
addRows(where, [buildRow(key, value)]);
}
if ("info" in data) {
apzInfo = formatApzInfo(data.info);
const trs = sortedArrayFromObject(data.info).map(function ([prop, val]) {
const td = $.new("td", String(val));
td.style["word-break"] = "break-all";
return $.new("tr", [$.new("th", prop, "column"), td]);
});
addRows("diagnostics", trs);
delete data.info;
}
const windowUtils = window.windowUtils;
const gpuProcessPid = windowUtils.gpuProcessPid;
if (gpuProcessPid != -1) {
let gpuProcessKillButton = null;
if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) {
gpuProcessKillButton = $.new("button");
gpuProcessKillButton.addEventListener("click", function () {
windowUtils.terminateGPUProcess();
});
document.l10n.setAttributes(
gpuProcessKillButton,
"gpu-process-kill-button"
);
}
addRow("diagnostics", "gpu-process-pid", [new Text(gpuProcessPid)]);
if (gpuProcessKillButton) {
addRow("diagnostics", "gpu-process", [gpuProcessKillButton]);
}
}
if (
(AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) &&
AppConstants.platform != "macosx"
) {
const gpuDeviceResetButton = $.new("button");
gpuDeviceResetButton.addEventListener("click", function () {
windowUtils.triggerDeviceReset();
});
document.l10n.setAttributes(
gpuDeviceResetButton,
"gpu-device-reset-button"
);
addRow("diagnostics", "gpu-device-reset", [gpuDeviceResetButton]);
}
// graphics-failures-tbody tbody
if ("failures" in data) {
// If indices is there, it should be the same length as failures,
// (see Troubleshoot.sys.mjs) but we check anyway:
if ("indices" in data && data.failures.length == data.indices.length) {
const combined = [];
for (let i = 0; i < data.failures.length; i++) {
const assembled = assembleFromGraphicsFailure(i, data);
combined.push(assembled);
}
combined.sort(function (a, b) {
if (a.index < b.index) {
return -1;
}
if (a.index > b.index) {
return 1;
}
return 0;
});
$.append(
$("graphics-failures-tbody"),
combined.map(function (val) {
return $.new("tr", [
$.new("th", val.header, "column"),
$.new("td", val.message),
]);
})
);
delete data.indices;
} else {
$.append($("graphics-failures-tbody"), [
$.new("tr", [
$.new("th", "LogFailure", "column"),
$.new(
"td",
data.failures.map(function (val) {
return $.new("p", val);
})
),
]),
]);
}
delete data.failures;
} else {
$("graphics-failures-tbody").style.display = "none";
}
// Add a new row to the table, and take the key (or keys) out of data.
//
// @where Table section to add to.
// @key Data key to use.
// @colKey The localization key to use, if different from key.
async function addRowFromKey(where, key, colKey) {
if (!(key in data)) {
return;
}
colKey = colKey || key;
let value;
const messageKey = key + "Message";
if (messageKey in data) {
value = await localizedMsg(data[messageKey]);
delete data[messageKey];
} else {
value = data[key];
}
delete data[key];
if (value) {
addRow(where, colKey, [new Text(value)]);
}
}
// graphics-features-tbody
let compositor = "";
if (data.windowLayerManagerRemote) {
compositor = data.windowLayerManagerType;
} else {
const noOMTCString = await document.l10n.formatValue(
"main-thread-no-omtc"
);
compositor = "BasicLayers (" + noOMTCString + ")";
}
addRow("features", "compositing", [new Text(compositor)]);
addRow("features", "supportFontDetermination", [
new Text(data.supportFontDetermination),
]);
delete data.windowLayerManagerRemote;
delete data.windowLayerManagerType;
delete data.numTotalWindows;
delete data.numAcceleratedWindows;
delete data.numAcceleratedWindowsMessage;
addRow(
"features",
"asyncPanZoom",
apzInfo.length
? [
new Text(
(
await document.l10n.formatValues(
apzInfo.map(id => {
return { id };
})
)
).join("; ")
),
]
: "apz-none"
);
const featureKeys = [
"webgl1WSIInfo",
"webgl1Renderer",
"webgl1Version",
"webgl1DriverExtensions",
"webgl1Extensions",
"webgl2WSIInfo",
"webgl2Renderer",
"webgl2Version",
"webgl2DriverExtensions",
"webgl2Extensions",
["supportsHardwareH264", "hardware-h264"],
["direct2DEnabled", "#Direct2D"],
["windowProtocol", "graphics-window-protocol"],
["desktopEnvironment", "graphics-desktop-environment"],
"usesTiling",
"targetFrameRate",
];
for (const feature of featureKeys) {
if (Array.isArray(feature)) {
await addRowFromKey("features", feature[0], feature[1]);
continue;
}
await addRowFromKey("features", feature);
}
if ("directWriteEnabled" in data) {
let message = data.directWriteEnabled;
if ("directWriteVersion" in data) {
message += " (" + data.directWriteVersion + ")";
}
await addRow("features", "#DirectWrite", [new Text(message)]);
delete data.directWriteEnabled;
delete data.directWriteVersion;
}
// Adapter tbodies.
const adapterKeys = [
["adapterDescription", "gpu-description"],
["adapterVendorID", "gpu-vendor-id"],
["adapterDeviceID", "gpu-device-id"],
["driverVendor", "gpu-driver-vendor"],
["driverVersion", "gpu-driver-version"],
["driverDate", "gpu-driver-date"],
["adapterDrivers", "gpu-drivers"],
["adapterSubsysID", "gpu-subsys-id"],
["adapterRAM", "gpu-ram"],
];
function showGpu(id, suffix) {
function get(prop) {
return data[prop + suffix];
}
const trs = [];
for (const [prop, key] of adapterKeys) {
const value = get(prop);
if (value === undefined || value === "") {
continue;
}
trs.push(buildRow(key, [new Text(value)]));
}
if (trs.length == 0) {
$("graphics-" + id + "-tbody").style.display = "none";
return;
}
let active = "yes";
if ("isGPU2Active" in data && (suffix == "2") != data.isGPU2Active) {
active = "no";
}
addRow(id, "gpu-active", active);
addRows(id, trs);
}
showGpu("gpu-1", "");
showGpu("gpu-2", "2");
// Remove adapter keys.
for (const [prop /* key */] of adapterKeys) {
delete data[prop];
delete data[prop + "2"];
}
delete data.isGPU2Active;
const featureLog = data.featureLog;
delete data.featureLog;
if (featureLog.features.length) {
for (const feature of featureLog.features) {
const trs = [];
for (const entry of feature.log) {
let contents;
if (!entry.hasOwnProperty("message")) {
// This is a default entry.
contents = entry.status + " by " + entry.type;
} else if (entry.message.length && entry.message[0] == "#") {
// This is a failure ID. See nsIGfxInfo.idl.
const m = /#BLOCKLIST_FEATURE_FAILURE_BUG_(\d+)/.exec(
entry.message
);
if (m) {
const bugSpan = $.new("span");
const bugHref = $.new("a");
bugHref.href =
"https://bugzilla.mozilla.org/show_bug.cgi?id=" + m[1];
bugHref.setAttribute("data-l10n-name", "bug-link");
bugSpan.append(bugHref);
document.l10n.setAttributes(bugSpan, "support-blocklisted-bug", {
bugNumber: m[1],
});
contents = [bugSpan];
} else {
const unknownFailure = $.new("span");
document.l10n.setAttributes(unknownFailure, "unknown-failure", {
failureCode: entry.message.substr(1),
});
contents = [unknownFailure];
}
} else {
contents =
entry.status + " by " + entry.type + ": " + entry.message;
}
trs.push($.new("tr", [$.new("td", contents)]));
}
addRow("decisions", "#" + feature.name, [$.new("table", trs)]);
}
} else {
$("graphics-decisions-tbody").style.display = "none";
}
if (featureLog.fallbacks.length) {
for (const fallback of featureLog.fallbacks) {
addRow("workarounds", "#" + fallback.name, [
new Text(fallback.message),
]);
}
} else {
$("graphics-workarounds-tbody").style.display = "none";
}
const crashGuards = data.crashGuards;
delete data.crashGuards;
if (crashGuards.length) {
for (const guard of crashGuards) {
const resetButton = $.new("button");
const onClickReset = function () {
Services.prefs.setIntPref(guard.prefName, 0);
resetButton.removeEventListener("click", onClickReset);
resetButton.disabled = true;
};
document.l10n.setAttributes(resetButton, "reset-on-next-restart");
resetButton.addEventListener("click", onClickReset);
addRow("crashguards", guard.type + "CrashGuard", [resetButton]);
}
} else {
$("graphics-crashguards-tbody").style.display = "none";
}
// Now that we're done, grab any remaining keys in data and drop them into
// the diagnostics section.
for (const key in data) {
const value = data[key];
addRow("diagnostics", key, [new Text(value)]);
}
},