1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
8 const {Troubleshoot} = ChromeUtils.import("resource://gre/modules/Troubleshoot.jsm");
9 const {ResetProfile} = ChromeUtils.import("resource://gre/modules/ResetProfile.jsm");
10 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
12 ChromeUtils.defineModuleGetter(this, "PluralForm",
13 "resource://gre/modules/PluralForm.jsm");
14 ChromeUtils.defineModuleGetter(this, "PlacesDBUtils",
15 "resource://gre/modules/PlacesDBUtils.jsm");
17 window.addEventListener("load", function onload(event) {
19 window.removeEventListener("load", onload);
20 Troubleshoot.snapshot(async function(snapshot) {
21 for (let prop in snapshotFormatters)
22 await snapshotFormatters[prop](snapshot[prop]);
25 setupEventListeners();
27 Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack);
31 // Fluent uses lisp-case IDs so this converts
32 // the SentenceCase info IDs to lisp-case.
33 const FLUENT_IDENT_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
34 function toFluentID(str) {
35 if (!FLUENT_IDENT_REGEX.test(str)) {
38 return str.toString().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
41 // Each property in this object corresponds to a property in Troubleshoot.jsm's
42 // snapshot data. Each function is passed its property's corresponding data,
43 // and it's the function's job to update the page with it.
44 var snapshotFormatters = {
46 async application(data) {
47 $("application-box").textContent = data.name;
48 $("useragent-box").textContent = data.userAgent;
49 $("os-box").textContent = data.osVersion;
50 $("supportLink").href = data.supportURL;
51 let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
53 version += " (" + data.vendor + ")";
54 $("version-box").textContent = version;
55 $("buildid-box").textContent = data.buildID;
56 if (data.updateChannel)
57 $("updatechannel-box").textContent = data.updateChannel;
58 $("profile-dir-box").textContent = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
61 let launcherStatusTextId = "launcher-process-status-unknown";
62 switch (data.launcherProcessState) {
66 launcherStatusTextId = "launcher-process-status-" + data.launcherProcessState;
70 document.l10n.setAttributes($("launcher-process-box"), launcherStatusTextId);
73 let statusTextId = "multi-process-status-unknown";
75 // Whitelist of known values with string descriptions:
76 switch (data.autoStartStatus) {
84 statusTextId = "multi-process-status-" + data.autoStartStatus;
88 document.l10n.setAttributes($("multiprocess-box-process-count"),
89 "multi-process-windows",
91 remoteWindows: data.numRemoteWindows,
92 totalWindows: data.numTotalWindows,
94 document.l10n.setAttributes($("multiprocess-box-status"), statusTextId);
96 if (data.remoteAutoStart) {
97 $("contentprocesses-box").textContent = data.currentContentProcesses +
99 data.maxContentProcesses;
101 $("contentprocesses-row").hidden = true;
104 if (Services.policies) {
105 let policiesStrId = "";
106 let aboutPolicies = "about:policies";
107 switch (data.policiesStatus) {
108 case Services.policies.INACTIVE:
109 policiesStrId = "policies-inactive";
112 case Services.policies.ACTIVE:
113 policiesStrId = "policies-active";
114 aboutPolicies += "#active";
118 policiesStrId = "policies-error";
119 aboutPolicies += "#errors";
123 if (data.policiesStatus != Services.policies.INACTIVE) {
124 let activePolicies = $.new("a", null, null, {
127 document.l10n.setAttributes(activePolicies, policiesStrId);
128 $("policies-status").appendChild(activePolicies);
130 document.l10n.setAttributes($("policies-status"), policiesStrId);
133 $("policies-status-row").hidden = true;
136 let keyLocationServiceGoogleFound = data.keyLocationServiceGoogleFound ? "found" : "missing";
137 document.l10n.setAttributes($("key-location-service-google-box"), keyLocationServiceGoogleFound);
139 let keySafebrowsingGoogleFound = data.keySafebrowsingGoogleFound ? "found" : "missing";
140 document.l10n.setAttributes($("key-safebrowsing-google-box"), keySafebrowsingGoogleFound);
142 let keyMozillaFound = data.keyMozillaFound ? "found" : "missing";
143 document.l10n.setAttributes($("key-mozilla-box"), keyMozillaFound);
145 $("safemode-box").textContent = data.safeMode;
149 if (!AppConstants.MOZ_CRASHREPORTER)
152 let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
153 document.l10n.setAttributes($("crashes-title"), "report-crash-for-days", { days: daysRange });
156 reportURL = Services.prefs.getCharPref("breakpad.reportURL");
157 // Ignore any non http/https urls
158 if (!/^https?:/i.test(reportURL))
162 $("crashes-noConfig").style.display = "block";
163 $("crashes-noConfig").classList.remove("no-copy");
166 $("crashes-allReports").style.display = "block";
167 $("crashes-allReports").classList.remove("no-copy");
169 if (data.pending > 0) {
170 document.l10n.setAttributes($("crashes-allReportsWithPending"), "pending-reports", { reports: data.pending });
173 let dateNow = new Date();
174 $.append($("crashes-tbody"), data.submitted.map(function(crash) {
175 let date = new Date(crash.date);
176 let timePassed = dateNow - date;
177 let formattedDateStrId;
178 let formattedDateStrArgs;
179 if (timePassed >= 24 * 60 * 60 * 1000) {
180 let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000));
181 formattedDateStrId = "crashes-time-days";
182 formattedDateStrArgs = { days: daysPassed };
183 } else if (timePassed >= 60 * 60 * 1000) {
184 let hoursPassed = Math.round(timePassed / (60 * 60 * 1000));
185 formattedDateStrId = "crashes-time-hours";
186 formattedDateStrArgs = { hours: hoursPassed };
188 let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1);
189 formattedDateStrId = "crashes-time-minutes";
190 formattedDateStrArgs = { minutes: minutesPassed };
194 $.new("a", crash.id, null, {href: reportURL + crash.id}),
196 $.new("td", null, null, {"data-l10n-id": formattedDateStrId, "data-l10n-args": formattedDateStrArgs}),
202 $.append($("extensions-tbody"), data.map(function(extension) {
204 $.new("td", extension.name),
205 $.new("td", extension.version),
206 $.new("td", extension.isActive),
207 $.new("td", extension.id),
212 securitySoftware(data) {
213 if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
214 $("security-software-title").hidden = true;
215 $("security-software-table").hidden = true;
219 $("security-software-antivirus").textContent = data.registeredAntiVirus;
220 $("security-software-antispyware").textContent = data.registeredAntiSpyware;
221 $("security-software-firewall").textContent = data.registeredFirewall;
225 $.append($("features-tbody"), data.map(function(feature) {
227 $.new("td", feature.name),
228 $.new("td", feature.version),
229 $.new("td", feature.id),
234 modifiedPreferences(data) {
235 $.append($("prefs-tbody"), sortedArrayFromObject(data).map(
236 function([name, value]) {
238 $.new("td", name, "pref-name"),
239 // Very long preference values can cause users problems when they
240 // copy and paste them into some text editors. Long values generally
241 // aren't useful anyway, so truncate them to a reasonable length.
242 $.new("td", String(value).substr(0, 120), "pref-value"),
248 lockedPreferences(data) {
249 $.append($("locked-prefs-tbody"), sortedArrayFromObject(data).map(
250 function([name, value]) {
252 $.new("td", name, "pref-name"),
253 $.new("td", String(value).substr(0, 120), "pref-value"),
259 async graphics(data) {
260 function localizedMsg(msg) {
261 if (typeof msg == "object" && msg.key) {
262 return document.l10n.formatValue(msg.key, msg.args);
264 let msgId = toFluentID(msg);
266 return document.l10n.formatValue(msgId);
271 // Read APZ info out of data.info, stripping it out in the process.
273 let formatApzInfo = function(info) {
275 for (let type of ["Wheel", "Touch", "Drag", "Keyboard", "Autoscroll"]) {
276 let key = "Apz" + type + "Input";
283 out.push(toFluentID(type.toLowerCase() + "Enabled"));
289 // Create a <tr> element with key and value columns.
291 // @key Text in the key column. Localized automatically, unless starts with "#".
292 // @value Fluent ID for text in the value column, or array of children.
293 function buildRow(key, value) {
294 let title = key[0] == "#" ? key.substr(1) : key;
295 let keyStrId = toFluentID(key);
296 let valueStrId = Array.isArray(value) ? null : toFluentID(value);
297 let td = $.new("td", value);
298 td.style["white-space"] = "pre-wrap";
300 document.l10n.setAttributes(td, valueStrId);
303 let th = $.new("th", title, "column");
304 if (!key.startsWith("#")) {
305 document.l10n.setAttributes(th, keyStrId);
313 // @where The name in "graphics-<name>-tbody", of the element to append to.
314 // @trs Array of row elements.
315 function addRows(where, trs) {
316 $.append($("graphics-" + where + "-tbody"), trs);
319 // Build and append a row.
321 // @where The name in "graphics-<name>-tbody", of the element to append to.
322 function addRow(where, key, value) {
323 addRows(where, [buildRow(key, value)]);
325 if ("info" in data) {
326 apzInfo = formatApzInfo(data.info);
328 let trs = sortedArrayFromObject(data.info).map(function([prop, val]) {
330 $.new("th", prop, "column"),
331 $.new("td", String(val)),
334 addRows("diagnostics", trs);
339 let windowUtils = window.windowUtils;
340 let gpuProcessPid = windowUtils.gpuProcessPid;
342 if (gpuProcessPid != -1) {
343 let gpuProcessKillButton = null;
344 if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) {
345 gpuProcessKillButton = $.new("button");
347 gpuProcessKillButton.addEventListener("click", function() {
348 windowUtils.terminateGPUProcess();
351 document.l10n.setAttributes(gpuProcessKillButton, "gpu-process-kill-button");
354 addRow("diagnostics", "gpu-process-pid", [new Text(gpuProcessPid)]);
355 if (gpuProcessKillButton) {
356 addRow("diagnostics", "gpu-process", [gpuProcessKillButton]);
360 if ((AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) && AppConstants.platform != "macosx") {
361 let gpuDeviceResetButton = $.new("button");
363 gpuDeviceResetButton.addEventListener("click", function() {
364 windowUtils.triggerDeviceReset();
367 document.l10n.setAttributes(gpuDeviceResetButton, "gpu-device-reset-button");
368 addRow("diagnostics", "gpu-device-reset", [gpuDeviceResetButton]);
371 // graphics-failures-tbody tbody
372 if ("failures" in data) {
373 // If indices is there, it should be the same length as failures,
374 // (see Troubleshoot.jsm) but we check anyway:
375 if ("indices" in data && data.failures.length == data.indices.length) {
377 for (let i = 0; i < data.failures.length; i++) {
378 let assembled = assembleFromGraphicsFailure(i, data);
379 combined.push(assembled);
381 combined.sort(function(a, b) {
382 if (a.index < b.index) return -1;
383 if (a.index > b.index) return 1;
386 $.append($("graphics-failures-tbody"),
387 combined.map(function(val) {
388 return $.new("tr", [$.new("th", val.header, "column"),
389 $.new("td", val.message)]);
393 $.append($("graphics-failures-tbody"),
394 [$.new("tr", [$.new("th", "LogFailure", "column"),
395 $.new("td", data.failures.map(function(val) {
396 return $.new("p", val);
399 delete data.failures;
401 $("graphics-failures-tbody").style.display = "none";
404 // Add a new row to the table, and take the key (or keys) out of data.
406 // @where Table section to add to.
407 // @key Data key to use.
408 // @colKey The localization key to use, if different from key.
409 async function addRowFromKey(where, key, colKey) {
412 colKey = colKey || key;
415 let messageKey = key + "Message";
416 if (messageKey in data) {
417 value = await localizedMsg(data[messageKey]);
418 delete data[messageKey];
425 addRow(where, colKey, [new Text(value)]);
429 // graphics-features-tbody
431 if (data.windowLayerManagerRemote) {
432 compositor = data.windowLayerManagerType;
433 if (data.windowUsingAdvancedLayers) {
434 compositor += " (Advanced Layers)";
437 let noOMTCString = await document.l10n.formatValue("main-thread-no-omtc");
438 compositor = "BasicLayers (" + noOMTCString + ")";
440 addRow("features", "compositing", [new Text(compositor)]);
441 delete data.windowLayerManagerRemote;
442 delete data.windowLayerManagerType;
443 delete data.numTotalWindows;
444 delete data.numAcceleratedWindows;
445 delete data.numAcceleratedWindowsMessage;
446 delete data.windowUsingAdvancedLayers;
448 addRow("features", "asyncPanZoom",
450 ? [new Text((await document.l10n.formatValues(apzInfo.map(id => { return {id}; }))).join("; "))]
456 "webgl1DriverExtensions",
461 "webgl2DriverExtensions",
463 ["supportsHardwareH264", "hardware-h264"],
464 ["direct2DEnabled", "#Direct2D"],
467 "offMainThreadPaintEnabled",
468 "offMainThreadPaintWorkerCount",
471 for (let feature of featureKeys) {
472 if (Array.isArray(feature)) {
473 await addRowFromKey("features", feature[0], feature[1]);
476 await addRowFromKey("features", feature);
479 if ("directWriteEnabled" in data) {
480 let message = data.directWriteEnabled;
481 if ("directWriteVersion" in data)
482 message += " (" + data.directWriteVersion + ")";
483 await addRow("features", "#DirectWrite", [new Text(message)]);
484 delete data.directWriteEnabled;
485 delete data.directWriteVersion;
490 ["adapterDescription", "gpu-description"],
491 ["adapterVendorID", "gpu-vendor-id"],
492 ["adapterDeviceID", "gpu-device-id"],
493 ["driverVersion", "gpu-driver-version"],
494 ["driverDate", "gpu-driver-date"],
495 ["adapterDrivers", "gpu-drivers"],
496 ["adapterSubsysID", "gpu-subsys-id"],
497 ["adapterRAM", "gpu-ram"],
500 function showGpu(id, suffix) {
502 return data[prop + suffix];
506 for (let [prop, key] of adapterKeys) {
507 let value = get(prop);
508 if (value === undefined || value === "")
510 trs.push(buildRow(key, value));
513 if (trs.length == 0) {
514 $("graphics-" + id + "-tbody").style.display = "none";
519 if ("isGPU2Active" in data && ((suffix == "2") != data.isGPU2Active)) {
523 addRow(id, "gpu-active", active);
526 showGpu("gpu-1", "");
527 showGpu("gpu-2", "2");
529 // Remove adapter keys.
530 for (let [prop /* key */] of adapterKeys) {
532 delete data[prop + "2"];
534 delete data.isGPU2Active;
536 let featureLog = data.featureLog;
537 delete data.featureLog;
540 for (let feature of featureLog.features) {
541 // Only add interesting decisions - ones that were not automatic based on
542 // all.js/gfxPrefs defaults.
543 if (feature.log.length > 1 || feature.log[0].status != "available") {
544 features.push(feature);
548 if (features.length) {
549 for (let feature of features) {
551 for (let entry of feature.log) {
552 if (entry.type == "default" && entry.status == "available")
556 if (entry.message.length > 0 && entry.message[0] == "#") {
557 // This is a failure ID. See nsIGfxInfo.idl.
558 let m = /#BLOCKLIST_FEATURE_FAILURE_BUG_(\d+)/.exec(entry.message);
560 let bugSpan = $.new("span");
561 document.l10n.setAttributes(bugSpan, "blocklisted-bug");
563 let bugHref = $.new("a");
564 bugHref.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + m[1];
565 document.l10n.setAttributes(bugHref, "bug-link", { bugNumber: m[1]});
567 contents = [bugSpan, bugHref];
569 let unknownFailure = $.new("span");
570 document.l10n.setAttributes(unknownFailure, "unknown-failure", { failureCode: entry.message.substr(1) });
571 contents = [unknownFailure];
574 contents = entry.status + " by " + entry.type + ": " + entry.message;
577 trs.push($.new("tr", [
578 $.new("td", contents),
581 addRow("decisions", "#" + feature.name, [$.new("table", trs)]);
584 $("graphics-decisions-tbody").style.display = "none";
587 if (featureLog.fallbacks.length) {
588 for (let fallback of featureLog.fallbacks) {
589 addRow("workarounds", fallback.name, [new Text(fallback.message)]);
592 $("graphics-workarounds-tbody").style.display = "none";
595 let crashGuards = data.crashGuards;
596 delete data.crashGuards;
598 if (crashGuards.length) {
599 for (let guard of crashGuards) {
600 let resetButton = $.new("button");
601 let onClickReset = function() {
602 Services.prefs.setIntPref(guard.prefName, 0);
603 resetButton.removeEventListener("click", onClickReset);
604 resetButton.disabled = true;
607 document.l10n.setAttributes(resetButton, "reset-on-next-restart");
608 resetButton.addEventListener("click", onClickReset);
610 addRow("crashguards", guard.type + "CrashGuard", [resetButton]);
613 $("graphics-crashguards-tbody").style.display = "none";
616 // Now that we're done, grab any remaining keys in data and drop them into
617 // the diagnostics section.
618 for (let key in data) {
619 let value = data[key];
620 addRow("diagnostics", key, [new Text(value)]);
625 function insertBasicInfo(key, value) {
626 function createRow(key, value) {
627 let th = $.new("th", null, "column");
628 document.l10n.setAttributes(th, key);
629 let td = $.new("td", value);
630 td.style["white-space"] = "pre-wrap";
632 return $.new("tr", [th, td]);
634 $.append($("media-info-tbody"), [createRow(key, value)]);
637 function createDeviceInfoRow(device) {
638 let deviceInfo = Ci.nsIAudioDeviceInfo;
641 states[deviceInfo.STATE_DISABLED] = "Disabled";
642 states[deviceInfo.STATE_UNPLUGGED] = "Unplugged";
643 states[deviceInfo.STATE_ENABLED] = "Enabled";
646 preferreds[deviceInfo.PREF_NONE] = "None";
647 preferreds[deviceInfo.PREF_MULTIMEDIA] = "Multimedia";
648 preferreds[deviceInfo.PREF_VOICE] = "Voice";
649 preferreds[deviceInfo.PREF_NOTIFICATION] = "Notification";
650 preferreds[deviceInfo.PREF_ALL] = "All";
653 formats[deviceInfo.FMT_S16LE] = "S16LE";
654 formats[deviceInfo.FMT_S16BE] = "S16BE";
655 formats[deviceInfo.FMT_F32LE] = "F32LE";
656 formats[deviceInfo.FMT_F32BE] = "F32BE";
658 function toPreferredString(preferred) {
659 if (preferred == deviceInfo.PREF_NONE) {
660 return preferreds[deviceInfo.PREF_NONE];
661 } else if (preferred & deviceInfo.PREF_ALL) {
662 return preferreds[deviceInfo.PREF_ALL];
665 for (let pref of [deviceInfo.PREF_MULTIMEDIA,
666 deviceInfo.PREF_VOICE,
667 deviceInfo.PREF_NOTIFICATION]) {
668 if (preferred & pref) {
669 str += " " + preferreds[pref];
675 function toFromatString(dev) {
676 let str = "default: " + formats[dev.defaultFormat] + ", support:";
677 for (let fmt of [deviceInfo.FMT_S16LE,
678 deviceInfo.FMT_S16BE,
679 deviceInfo.FMT_F32LE,
680 deviceInfo.FMT_F32BE]) {
681 if (dev.supportedFormat & fmt) {
682 str += " " + formats[fmt];
688 function toRateString(dev) {
689 return "default: " + dev.defaultRate +
690 ", support: " + dev.minRate + " - " + dev.maxRate;
693 function toLatencyString(dev) {
694 return dev.minLatency + " - " + dev.maxLatency;
697 return $.new("tr", [$.new("td", device.name),
698 $.new("td", device.groupId),
699 $.new("td", device.vendor),
700 $.new("td", states[device.state]),
701 $.new("td", toPreferredString(device.preferred)),
702 $.new("td", toFromatString(device)),
703 $.new("td", device.maxChannels),
704 $.new("td", toRateString(device)),
705 $.new("td", toLatencyString(device))]);
708 function insertDeviceInfo(side, devices) {
710 for (let dev of devices) {
711 rows.push(createDeviceInfoRow(dev));
713 $.append($("media-" + side + "-devices-tbody"), rows);
717 insertBasicInfo("audio-backend", data.currentAudioBackend);
718 insertBasicInfo("max-audio-channels", data.currentMaxAudioChannels);
719 insertBasicInfo("sample-rate", data.currentPreferredSampleRate);
721 // Output devices information
722 insertDeviceInfo("output", data.audioOutputDevices);
724 // Input devices information
725 insertDeviceInfo("input", data.audioInputDevices);
729 $("javascript-incremental-gc").textContent = data.incrementalGCEnabled;
732 accessibility(data) {
733 $("a11y-activated").textContent = data.isActive;
734 $("a11y-force-disabled").textContent = data.forceDisabled || 0;
736 let a11yHandlerUsed = $("a11y-handler-used");
737 if (a11yHandlerUsed) {
738 a11yHandlerUsed.textContent = data.handlerUsed;
741 let a11yInstantiator = $("a11y-instantiator");
742 if (a11yInstantiator) {
743 a11yInstantiator.textContent = data.instantiator;
747 libraryVersions(data) {
751 $.new("th", null, null, {"data-l10n-id": "min-lib-versions"}),
752 $.new("th", null, null, {"data-l10n-id": "loaded-lib-versions"}),
755 sortedArrayFromObject(data).forEach(
756 function([name, val]) {
757 trs.push($.new("tr", [
759 $.new("td", val.minVersion),
760 $.new("td", val.version),
764 $.append($("libversions-tbody"), trs);
770 let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
771 userJSFile.append("user.js");
772 $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
773 $("prefs-user-js-section").style.display = "";
774 // Clear the no-copy class
775 $("prefs-user-js-section").className = "";
779 if (!AppConstants.MOZ_SANDBOX)
782 let tbody = $("sandbox-tbody");
783 for (let key in data) {
784 // Simplify the display a little in the common case.
785 if (key === "hasPrivilegedUserNamespaces" &&
786 data[key] === data.hasUserNamespaces) {
789 if (key === "syscallLog") {
790 // Not in this table.
793 let keyStrId = toFluentID(key);
794 let th = $.new("th", null, "column");
795 document.l10n.setAttributes(th, keyStrId);
796 tbody.appendChild($.new("tr", [
798 $.new("td", data[key]),
802 if ("syscallLog" in data) {
803 let syscallBody = $("sandbox-syscalls-tbody");
804 let argsHead = $("sandbox-syscalls-argshead");
805 for (let syscall of data.syscallLog) {
806 if (argsHead.colSpan < syscall.args.length) {
807 argsHead.colSpan = syscall.args.length;
809 let procTypeStrId = toFluentID(syscall.procType);
811 $.new("td", syscall.index, "integer"),
812 $.new("td", syscall.msecAgo / 1000),
813 $.new("td", syscall.pid, "integer"),
814 $.new("td", syscall.tid, "integer"),
815 $.new("td", null, null, {"data-l10n-id": "sandbox-proc-type-" + procTypeStrId}),
816 $.new("td", syscall.syscall, "integer"),
818 for (let arg of syscall.args) {
819 cells.push($.new("td", arg, "integer"));
821 syscallBody.appendChild($.new("tr", cells));
827 $("intl-locale-requested").textContent =
828 JSON.stringify(data.localeService.requested);
829 $("intl-locale-available").textContent =
830 JSON.stringify(data.localeService.available);
831 $("intl-locale-supported").textContent =
832 JSON.stringify(data.localeService.supported);
833 $("intl-locale-regionalprefs").textContent =
834 JSON.stringify(data.localeService.regionalPrefs);
835 $("intl-locale-default").textContent =
836 JSON.stringify(data.localeService.defaultLocale);
838 $("intl-osprefs-systemlocales").textContent =
839 JSON.stringify(data.osPrefs.systemLocales);
840 $("intl-osprefs-regionalprefs").textContent =
841 JSON.stringify(data.osPrefs.regionalPrefsLocales);
845 var $ = document.getElementById.bind(document);
847 $.new = function $_new(tag, textContentOrChildren, className, attributes) {
848 let elt = document.createElement(tag);
850 elt.className = className;
853 if (attributes["data-l10n-id"]) {
854 let args = attributes.hasOwnProperty("data-l10n-args") ?
855 attributes["data-l10n-args"] :
857 document.l10n.setAttributes(elt,
858 attributes["data-l10n-id"],
860 delete attributes["data-l10n-id"];
862 delete attributes["data-l10n-args"];
866 for (let attrName in attributes) {
867 elt.setAttribute(attrName, attributes[attrName]);
870 if (Array.isArray(textContentOrChildren)) {
871 this.append(elt, textContentOrChildren);
872 } else if (!attributes || !attributes["data-l10n-id"]) {
873 elt.textContent = String(textContentOrChildren);
878 $.append = function $_append(parent, children) {
879 children.forEach(c => parent.appendChild(c));
882 function assembleFromGraphicsFailure(i, data) {
883 // Only cover the cases we have today; for example, we do not have
884 // log failures that assert and we assume the log level is 1/error.
885 let message = data.failures[i];
886 let index = data.indices[i];
888 if (message.search(/\[GFX1-\]: \(LF\)/) == 0) {
889 // Non-asserting log failure - the message is substring(14)
891 message = message.substring(14);
892 } else if (message.search(/\[GFX1-\]: /) == 0) {
893 // Non-asserting - the message is substring(9)
895 message = message.substring(9);
896 } else if (message.search(/\[GFX1\]: /) == 0) {
897 // Asserting - the message is substring(8)
899 message = message.substring(8);
901 let assembled = {"index": index,
902 "header": ("(#" + index + ") " + what),
907 function sortedArrayFromObject(obj) {
909 for (let prop in obj)
910 tuples.push([prop, obj[prop]]);
911 tuples.sort(([prop1, v1], [prop2, v2]) => prop1.localeCompare(prop2));
915 function copyRawDataToClipboard(button) {
917 button.disabled = true;
919 Troubleshoot.snapshot(async function(snapshot) {
921 button.disabled = false;
922 let str = Cc["@mozilla.org/supports-string;1"].
923 createInstance(Ci.nsISupportsString);
924 str.data = JSON.stringify(snapshot, undefined, 2);
925 let transferable = Cc["@mozilla.org/widget/transferable;1"].
926 createInstance(Ci.nsITransferable);
927 transferable.init(getLoadContext());
928 transferable.addDataFlavor("text/unicode");
929 transferable.setTransferData("text/unicode", str, str.data.length * 2);
930 Services.clipboard.setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
931 if (AppConstants.platform == "android") {
932 // Present a snackbar notification.
933 var {Snackbars} = ChromeUtils.import("resource://gre/modules/Snackbars.jsm");
934 let rawDataCopiedString = await document.l10n.formatValue("raw-data-copied");
935 Snackbars.show(rawDataCopiedString, Snackbars.LENGTH_SHORT);
940 button.disabled = false;
945 function getLoadContext() {
946 return window.docShell.QueryInterface(Ci.nsILoadContext);
949 async function copyContentsToClipboard() {
950 // Get the HTML and text representations for the important part of the page.
951 let contentsDiv = $("contents");
952 let dataHtml = contentsDiv.innerHTML;
953 let dataText = createTextForElement(contentsDiv);
955 // We can't use plain strings, we have to use nsSupportsString.
956 let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
957 let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
958 let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);
960 let transferable = Cc["@mozilla.org/widget/transferable;1"]
961 .createInstance(Ci.nsITransferable);
962 transferable.init(getLoadContext());
964 // Add the HTML flavor.
965 transferable.addDataFlavor("text/html");
966 ssHtml.data = dataHtml;
967 transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2);
969 // Add the plain text flavor.
970 transferable.addDataFlavor("text/unicode");
971 ssText.data = dataText;
972 transferable.setTransferData("text/unicode", ssText, dataText.length * 2);
974 // Store the data into the clipboard.
975 Services.clipboard.setData(transferable, null, Services.clipboard.kGlobalClipboard);
977 if (AppConstants.platform == "android") {
978 // Present a snackbar notification.
979 var {Snackbars} = ChromeUtils.import("resource://gre/modules/Snackbars.jsm");
980 let textCopiedString = await document.l10n.formatValue("text-copied");
981 Snackbars.show(textCopiedString, Snackbars.LENGTH_SHORT);
985 // Return the plain text representation of an element. Do a little bit
986 // of pretty-printing to make it human-readable.
987 function createTextForElement(elem) {
988 let serializer = new Serializer();
989 let text = serializer.serialize(elem);
991 // Actual CR/LF pairs are needed for some Windows text editors.
992 if (AppConstants.platform == "win") {
993 text = text.replace(/\n/g, "\r\n");
999 function Serializer() {
1002 Serializer.prototype = {
1004 serialize(rootElem) {
1006 this._startNewLine();
1007 this._serializeElement(rootElem);
1008 this._startNewLine();
1009 return this._lines.join("\n").trim() + "\n";
1012 // The current line is always the line that writing will start at next. When
1013 // an element is serialized, the current line is updated to be the line at
1014 // which the next element should be written.
1015 get _currentLine() {
1016 return this._lines.length ? this._lines[this._lines.length - 1] : null;
1019 set _currentLine(val) {
1020 return this._lines[this._lines.length - 1] = val;
1023 _serializeElement(elem) {
1024 if (this._ignoreElement(elem))
1028 if (elem.localName == "table") {
1029 this._serializeTable(elem);
1033 // all other elements
1035 let hasText = false;
1036 for (let child of elem.childNodes) {
1037 if (child.nodeType == Node.TEXT_NODE) {
1038 let text = this._nodeText(child);
1039 this._appendText(text);
1040 hasText = hasText || !!text.trim();
1041 } else if (child.nodeType == Node.ELEMENT_NODE) {
1042 this._serializeElement(child);
1046 // For headings, draw a "line" underneath them so they stand out.
1047 if (/^h[0-9]+$/.test(elem.localName)) {
1048 let headerText = (this._currentLine || "").trim();
1050 this._startNewLine();
1051 this._appendText("-".repeat(headerText.length));
1055 // Add a blank line underneath block elements but only if they contain text.
1057 let display = window.getComputedStyle(elem).getPropertyValue("display");
1058 if (display == "block") {
1059 this._startNewLine();
1060 this._startNewLine();
1065 _startNewLine(lines) {
1066 let currLine = this._currentLine;
1068 // The current line is not empty. Trim it.
1069 this._currentLine = currLine.trim();
1070 if (!this._currentLine)
1071 // The current line became empty. Discard it.
1074 this._lines.push("");
1077 _appendText(text, lines) {
1078 this._currentLine += text;
1081 _isHiddenSubHeading(th) {
1082 return th.parentNode.parentNode.style.display == "none";
1085 _serializeTable(table) {
1086 // Collect the table's column headings if in fact there are any. First
1087 // check thead. If there's no thead, check the first tr.
1088 let colHeadings = {};
1089 let tableHeadingElem = table.querySelector("thead");
1090 if (!tableHeadingElem)
1091 tableHeadingElem = table.querySelector("tr");
1092 if (tableHeadingElem) {
1093 let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td");
1094 // If there's a contiguous run of th's in the children starting from the
1095 // rightmost child, then consider them to be column headings.
1096 for (let i = tableHeadingCols.length - 1; i >= 0; i--) {
1097 let col = tableHeadingCols[i];
1098 if (col.localName != "th" || col.classList.contains("title-column"))
1100 colHeadings[i] = this._nodeText(col).trim();
1103 let hasColHeadings = Object.keys(colHeadings).length > 0;
1104 if (!hasColHeadings)
1105 tableHeadingElem = null;
1107 let trs = table.querySelectorAll("table > tr, tbody > tr");
1109 tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0;
1111 if (startRow >= trs.length)
1112 // The table's empty.
1115 if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) {
1116 // Use column headings. Print each tr as a multi-line chunk like:
1117 // Heading 1: Column 1 value
1118 // Heading 2: Column 2 value
1119 for (let i = startRow; i < trs.length; i++) {
1120 if (this._ignoreElement(trs[i]))
1122 let children = trs[i].querySelectorAll("td");
1123 for (let j = 0; j < children.length; j++) {
1126 text += colHeadings[j] + ": ";
1127 text += this._nodeText(children[j]).trim();
1128 this._appendText(text);
1129 this._startNewLine();
1131 this._startNewLine();
1136 // Don't use column headings. Assume the table has only two columns and
1137 // print each tr in a single line like:
1138 // Column 1 value: Column 2 value
1139 for (let i = startRow; i < trs.length; i++) {
1140 if (this._ignoreElement(trs[i]))
1142 let children = trs[i].querySelectorAll("th,td");
1143 let rowHeading = this._nodeText(children[0]).trim();
1144 if (children[0].classList.contains("title-column")) {
1145 if (!this._isHiddenSubHeading(children[0]))
1146 this._appendText(rowHeading);
1147 } else if (children.length == 1) {
1148 // This is a single-cell row.
1149 this._appendText(rowHeading);
1151 let childTables = trs[i].querySelectorAll("table");
1152 if (childTables.length) {
1153 // If we have child tables, don't use nodeText - its trs are already
1154 // queued up from querySelectorAll earlier.
1155 this._appendText(rowHeading + ": ");
1157 this._appendText(rowHeading + ": " + this._nodeText(children[1]).trim());
1160 this._startNewLine();
1162 this._startNewLine();
1165 _ignoreElement(elem) {
1166 return elem.classList.contains("no-copy");
1170 return node.textContent.replace(/\s+/g, " ");
1174 function openProfileDirectory() {
1175 // Get the profile directory.
1176 let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
1177 let profileDir = currProfD.path;
1179 // Show the profile directory.
1180 let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
1181 "nsIFile", "initWithPath");
1182 new nsLocalFile(profileDir).reveal();
1186 * Profile reset is only supported for the default profile if the appropriate migrator exists.
1188 function populateActionBox() {
1189 if (ResetProfile.resetSupported()) {
1190 $("reset-box").style.display = "block";
1191 $("action-box").style.display = "block";
1193 if (!Services.appinfo.inSafeMode && AppConstants.platform !== "android") {
1194 $("safe-mode-box").style.display = "block";
1195 $("action-box").style.display = "block";
1197 if (Services.policies && !Services.policies.isAllowed("safeMode")) {
1198 $("restart-in-safe-mode-button").setAttribute("disabled", "true");
1203 // Prompt user to restart the browser in safe mode
1204 function safeModeRestart() {
1205 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
1206 .createInstance(Ci.nsISupportsPRBool);
1207 Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
1209 if (!cancelQuit.data) {
1210 Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
1214 * Set up event listeners for buttons.
1216 function setupEventListeners() {
1217 let button = $("show-update-history-button");
1219 button.addEventListener("click", function(event) {
1220 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
1221 prompter.showUpdateHistory(window);
1224 button = $("reset-box-button");
1226 button.addEventListener("click", function(event) {
1227 ResetProfile.openConfirmationDialog(window);
1230 button = $("restart-in-safe-mode-button");
1232 button.addEventListener("click", function(event) {
1233 if (Services.obs.enumerateObservers("restart-in-safe-mode").hasMoreElements()) {
1234 Services.obs.notifyObservers(null, "restart-in-safe-mode");
1240 button = $("verify-place-integrity-button");
1242 button.addEventListener("click", function(event) {
1243 PlacesDBUtils.checkAndFixDatabase().then((tasksStatusMap) => {
1245 for (let [key, value] of tasksStatusMap) {
1246 logs.push(`> Task: ${key}`);
1247 let prefix = value.succeeded ? "+ " : "- ";
1248 logs = logs.concat(value.logs.map(m => `${prefix}${m}`));
1250 $("verify-place-result").style.display = "block";
1251 $("verify-place-result").classList.remove("no-copy");
1252 $("verify-place-result").textContent = logs.join("\n");
1257 $("copy-raw-data-to-clipboard").addEventListener("click", function(event) {
1258 copyRawDataToClipboard(this);
1260 $("copy-to-clipboard").addEventListener("click", function(event) {
1261 copyContentsToClipboard();
1263 $("profile-dir-button").addEventListener("click", function(event) {
1264 openProfileDirectory();