aboutSupport.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  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/. */
  4. "use strict";
  5. var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
  6. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  7. Cu.import("resource://gre/modules/Services.jsm");
  8. Cu.import("resource://gre/modules/Troubleshoot.jsm");
  9. Cu.import("resource://gre/modules/ResetProfile.jsm");
  10. XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
  11. "resource://gre/modules/PluralForm.jsm");
  12. XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
  13. "resource://gre/modules/PlacesDBUtils.jsm");
  14. window.addEventListener("load", function onload(event) {
  15. try {
  16. window.removeEventListener("load", onload, false);
  17. Troubleshoot.snapshot(function (snapshot) {
  18. for (let prop in snapshotFormatters)
  19. snapshotFormatters[prop](snapshot[prop]);
  20. });
  21. populateActionBox();
  22. setupEventListeners();
  23. } catch (e) {
  24. Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack);
  25. }
  26. }, false);
  27. // Each property in this object corresponds to a property in Troubleshoot.jsm's
  28. // snapshot data. Each function is passed its property's corresponding data,
  29. // and it's the function's job to update the page with it.
  30. var snapshotFormatters = {
  31. application: function application(data) {
  32. $("application-box").textContent = data.name;
  33. $("useragent-box").textContent = data.userAgent;
  34. $("os-box").textContent = data.osVersion;
  35. $("binary-box").textContent = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
  36. $("supportLink").href = data.supportURL;
  37. let version = Services.appinfo.version;
  38. if (data.versionArch) {
  39. version += " (" + data.versionArch + ")";
  40. }
  41. if (data.vendor)
  42. version += " (" + data.vendor + ")";
  43. $("version-box").textContent = version;
  44. $("buildid-box").textContent = data.buildID;
  45. if (data.updateChannel)
  46. $("updatechannel-box").textContent = data.updateChannel;
  47. $("safemode-box").textContent = data.safeMode;
  48. },
  49. crashes: function crashes(data) {
  50. return;
  51. },
  52. extensions: function extensions(data) {
  53. $.append($("extensions-tbody"), data.map(function (extension) {
  54. return $.new("tr", [
  55. $.new("td", extension.name),
  56. $.new("td", extension.version),
  57. $.new("td", extension.isActive),
  58. $.new("td", extension.id),
  59. ]);
  60. }));
  61. },
  62. modifiedPreferences: function modifiedPreferences(data) {
  63. $.append($("prefs-tbody"), sortedArrayFromObject(data).map(
  64. function ([name, value]) {
  65. return $.new("tr", [
  66. $.new("td", name, "pref-name"),
  67. // Very long preference values can cause users problems when they
  68. // copy and paste them into some text editors. Long values generally
  69. // aren't useful anyway, so truncate them to a reasonable length.
  70. $.new("td", String(value).substr(0, 120), "pref-value"),
  71. ]);
  72. }
  73. ));
  74. },
  75. lockedPreferences: function lockedPreferences(data) {
  76. $.append($("locked-prefs-tbody"), sortedArrayFromObject(data).map(
  77. function ([name, value]) {
  78. return $.new("tr", [
  79. $.new("td", name, "pref-name"),
  80. $.new("td", String(value).substr(0, 120), "pref-value"),
  81. ]);
  82. }
  83. ));
  84. },
  85. graphics: function graphics(data) {
  86. let strings = stringBundle();
  87. function localizedMsg(msgArray) {
  88. let nameOrMsg = msgArray.shift();
  89. if (msgArray.length) {
  90. // formatStringFromName logs an NS_ASSERTION failure otherwise that says
  91. // "use GetStringFromName". Lame.
  92. try {
  93. return strings.formatStringFromName(nameOrMsg, msgArray,
  94. msgArray.length);
  95. }
  96. catch (err) {
  97. // Throws if nameOrMsg is not a name in the bundle. This shouldn't
  98. // actually happen though, since msgArray.length > 1 => nameOrMsg is a
  99. // name in the bundle, not a message, and the remaining msgArray
  100. // elements are parameters.
  101. return nameOrMsg;
  102. }
  103. }
  104. try {
  105. return strings.GetStringFromName(nameOrMsg);
  106. }
  107. catch (err) {
  108. // Throws if nameOrMsg is not a name in the bundle.
  109. }
  110. return nameOrMsg;
  111. }
  112. // Read APZ info out of data.info, stripping it out in the process.
  113. let apzInfo = [];
  114. let formatApzInfo = function (info) {
  115. let out = [];
  116. for (let type of ['Wheel', 'Touch', 'Drag']) {
  117. let key = 'Apz' + type + 'Input';
  118. if (!(key in info))
  119. continue;
  120. delete info[key];
  121. let message = localizedMsg([type.toLowerCase() + 'Enabled']);
  122. out.push(message);
  123. }
  124. return out;
  125. };
  126. // Create a <tr> element with key and value columns.
  127. //
  128. // @key Text in the key column. Localized automatically, unless starts with "#".
  129. // @value Text in the value column. Not localized.
  130. function buildRow(key, value) {
  131. let title;
  132. if (key[0] == "#") {
  133. title = key.substr(1);
  134. } else {
  135. try {
  136. title = strings.GetStringFromName(key);
  137. } catch (e) {
  138. title = key;
  139. }
  140. }
  141. let td = $.new("td", value);
  142. td.style["white-space"] = "pre-wrap";
  143. return $.new("tr", [
  144. $.new("th", title, "column"),
  145. td,
  146. ]);
  147. }
  148. // @where The name in "graphics-<name>-tbody", of the element to append to.
  149. // @trs Array of row elements.
  150. function addRows(where, trs) {
  151. $.append($("graphics-" + where + "-tbody"), trs);
  152. }
  153. // Build and append a row.
  154. //
  155. // @where The name in "graphics-<name>-tbody", of the element to append to.
  156. function addRow(where, key, value) {
  157. addRows(where, [buildRow(key, value)]);
  158. }
  159. if (data.clearTypeParameters !== undefined) {
  160. addRow("diagnostics", "clearTypeParameters", data.clearTypeParameters);
  161. }
  162. if ("info" in data) {
  163. apzInfo = formatApzInfo(data.info);
  164. let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) {
  165. return $.new("tr", [
  166. $.new("th", prop, "column"),
  167. $.new("td", String(val)),
  168. ]);
  169. });
  170. addRows("diagnostics", trs);
  171. delete data.info;
  172. }
  173. #ifdef NIGHTLY_BUILD
  174. let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
  175. .getInterface(Ci.nsIDOMWindowUtils);
  176. let gpuProcessPid = windowUtils.gpuProcessPid;
  177. if (gpuProcessPid != -1) {
  178. let gpuProcessKillButton = $.new("button");
  179. gpuProcessKillButton.addEventListener("click", function() {
  180. windowUtils.terminateGPUProcess();
  181. });
  182. gpuProcessKillButton.textContent = strings.GetStringFromName("gpuProcessKillButton");
  183. addRow("diagnostics", "GPUProcessPid", gpuProcessPid);
  184. addRow("diagnostics", "GPUProcess", [gpuProcessKillButton]);
  185. }
  186. #endif
  187. // graphics-failures-tbody tbody
  188. if ("failures" in data) {
  189. // If indices is there, it should be the same length as failures,
  190. // (see Troubleshoot.jsm) but we check anyway:
  191. if ("indices" in data && data.failures.length == data.indices.length) {
  192. let combined = [];
  193. for (let i = 0; i < data.failures.length; i++) {
  194. let assembled = assembleFromGraphicsFailure(i, data);
  195. combined.push(assembled);
  196. }
  197. combined.sort(function(a, b) {
  198. if (a.index < b.index) return -1;
  199. if (a.index > b.index) return 1;
  200. return 0;
  201. });
  202. $.append($("graphics-failures-tbody"),
  203. combined.map(function(val) {
  204. return $.new("tr", [$.new("th", val.header, "column"),
  205. $.new("td", val.message)]);
  206. }));
  207. delete data.indices;
  208. } else {
  209. $.append($("graphics-failures-tbody"),
  210. [$.new("tr", [$.new("th", "LogFailure", "column"),
  211. $.new("td", data.failures.map(function (val) {
  212. return $.new("p", val);
  213. }))])]);
  214. }
  215. } else {
  216. $("graphics-failures-tbody").style.display = "none";
  217. }
  218. // Add a new row to the table, and take the key (or keys) out of data.
  219. //
  220. // @where Table section to add to.
  221. // @key Data key to use.
  222. // @colKey The localization key to use, if different from key.
  223. function addRowFromKey(where, key, colKey) {
  224. if (!(key in data))
  225. return;
  226. colKey = colKey || key;
  227. let value;
  228. let messageKey = key + "Message";
  229. if (messageKey in data) {
  230. value = localizedMsg(data[messageKey]);
  231. delete data[messageKey];
  232. } else {
  233. value = data[key];
  234. }
  235. delete data[key];
  236. if (value) {
  237. addRow(where, colKey, value);
  238. }
  239. }
  240. // graphics-features-tbody
  241. let compositor = data.windowLayerManagerRemote
  242. ? data.windowLayerManagerType
  243. : "BasicLayers (" + strings.GetStringFromName("mainThreadNoOMTC") + ")";
  244. addRow("features", "compositing", compositor);
  245. let acceleratedWindows = data.numAcceleratedWindows + "/" + data.numTotalWindows;
  246. if (data.windowLayerManagerType) {
  247. acceleratedWindows += " " + data.windowLayerManagerType;
  248. }
  249. if (data.windowLayerManagerRemote) {
  250. acceleratedWindows += " (OMTC)";
  251. }
  252. if (data.numAcceleratedWindowsMessage) {
  253. acceleratedWindows += " " + localizedMsg(data.numAcceleratedWindowsMessage);
  254. }
  255. addRow("features", "acceleratedWindows", acceleratedWindows);
  256. delete data.windowLayerManagerRemote;
  257. delete data.windowLayerManagerType;
  258. delete data.numTotalWindows;
  259. delete data.numAcceleratedWindows;
  260. delete data.numAcceleratedWindowsMessage;
  261. addRow("features", "asyncPanZoom",
  262. apzInfo.length
  263. ? apzInfo.join("; ")
  264. : localizedMsg(["apzNone"]));
  265. addRowFromKey("features", "webgl1WSIInfo");
  266. addRowFromKey("features", "webgl1Renderer");
  267. addRowFromKey("features", "webgl1Version");
  268. addRowFromKey("features", "webgl1DriverExtensions");
  269. addRowFromKey("features", "webgl1Extensions");
  270. addRowFromKey("features", "webgl2WSIInfo");
  271. addRowFromKey("features", "webgl2Renderer");
  272. addRowFromKey("features", "webgl2Version");
  273. addRowFromKey("features", "webgl2DriverExtensions");
  274. addRowFromKey("features", "webgl2Extensions");
  275. addRowFromKey("features", "supportsHardwareH264", "hardwareH264");
  276. addRowFromKey("features", "currentAudioBackend", "audioBackend");
  277. addRowFromKey("features", "direct2DEnabled", "#Direct2D");
  278. if ("directWriteEnabled" in data) {
  279. let message = data.directWriteEnabled;
  280. if ("directWriteVersion" in data)
  281. message += " (" + data.directWriteVersion + ")";
  282. addRow("features", "#DirectWrite", message);
  283. delete data.directWriteEnabled;
  284. delete data.directWriteVersion;
  285. }
  286. // Adapter tbodies.
  287. let adapterKeys = [
  288. ["adapterDescription", "gpuDescription"],
  289. ["adapterVendorID", "gpuVendorID"],
  290. ["adapterDeviceID", "gpuDeviceID"],
  291. ["driverVersion", "gpuDriverVersion"],
  292. ["driverDate", "gpuDriverDate"],
  293. ["adapterDrivers", "gpuDrivers"],
  294. ["adapterSubsysID", "gpuSubsysID"],
  295. ["adapterRAM", "gpuRAM"],
  296. ];
  297. function showGpu(id, suffix) {
  298. function get(prop) {
  299. return data[prop + suffix];
  300. }
  301. let trs = [];
  302. for (let [prop, key] of adapterKeys) {
  303. let value = get(prop);
  304. if (value === undefined || value === "")
  305. continue;
  306. trs.push(buildRow(key, value));
  307. }
  308. if (trs.length == 0) {
  309. $("graphics-" + id + "-tbody").style.display = "none";
  310. return;
  311. }
  312. let active = "yes";
  313. if ("isGPU2Active" in data && ((suffix == "2") != data.isGPU2Active)) {
  314. active = "no";
  315. }
  316. addRow(id, "gpuActive", strings.GetStringFromName(active));
  317. addRows(id, trs);
  318. }
  319. showGpu("gpu-1", "");
  320. showGpu("gpu-2", "2");
  321. // Remove adapter keys.
  322. for (let [prop, key] of adapterKeys) {
  323. delete data[prop];
  324. delete data[prop + "2"];
  325. }
  326. delete data.isGPU2Active;
  327. let featureLog = data.featureLog;
  328. delete data.featureLog;
  329. let features = [];
  330. for (let feature of featureLog.features) {
  331. // Only add interesting decisions - ones that were not automatic based on
  332. // all.js/gfxPrefs defaults.
  333. if (feature.log.length > 1 || feature.log[0].status != "available") {
  334. features.push(feature);
  335. }
  336. }
  337. if (features.length) {
  338. for (let feature of features) {
  339. let trs = [];
  340. for (let entry of feature.log) {
  341. if (entry.type == "default" && entry.status == "available")
  342. continue;
  343. let contents;
  344. if (entry.message.length > 0 && entry.message[0] == "#") {
  345. // This is a failure ID. See nsIGfxInfo.idl.
  346. let m;
  347. if (m = /#BLOCKLIST_FEATURE_FAILURE_BUG_(\d+)/.exec(entry.message)) {
  348. let bugSpan = $.new("span");
  349. bugSpan.textContent = strings.GetStringFromName("blocklistedBug") + "; ";
  350. let bugHref = $.new("a");
  351. bugHref.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + m[1];
  352. bugHref.textContent = strings.formatStringFromName("bugLink", [m[1]], 1);
  353. contents = [bugSpan, bugHref];
  354. } else {
  355. contents = strings.formatStringFromName(
  356. "unknownFailure", [entry.message.substr(1)], 1);
  357. }
  358. } else {
  359. contents = entry.status + " by " + entry.type + ": " + entry.message;
  360. }
  361. trs.push($.new("tr", [
  362. $.new("td", contents),
  363. ]));
  364. }
  365. addRow("decisions", feature.name, [$.new("table", trs)]);
  366. }
  367. } else {
  368. $("graphics-decisions-tbody").style.display = "none";
  369. }
  370. if (featureLog.fallbacks.length) {
  371. for (let fallback of featureLog.fallbacks) {
  372. addRow("workarounds", fallback.name, fallback.message);
  373. }
  374. } else {
  375. $("graphics-workarounds-tbody").style.display = "none";
  376. }
  377. let crashGuards = data.crashGuards;
  378. delete data.crashGuards;
  379. if (crashGuards.length) {
  380. for (let guard of crashGuards) {
  381. let resetButton = $.new("button");
  382. let onClickReset = (function (guard) {
  383. // Note - need this wrapper until bug 449811 fixes |guard| scoping.
  384. return function () {
  385. Services.prefs.setIntPref(guard.prefName, 0);
  386. resetButton.removeEventListener("click", onClickReset);
  387. resetButton.disabled = true;
  388. };
  389. })(guard);
  390. resetButton.textContent = strings.GetStringFromName("resetOnNextRestart");
  391. resetButton.addEventListener("click", onClickReset);
  392. addRow("crashguards", guard.type + "CrashGuard", [resetButton]);
  393. }
  394. } else {
  395. $("graphics-crashguards-tbody").style.display = "none";
  396. }
  397. // Now that we're done, grab any remaining keys in data and drop them into
  398. // the diagnostics section.
  399. for (let key in data) {
  400. let value = data[key];
  401. if (Array.isArray(value)) {
  402. value = localizedMsg(value);
  403. }
  404. addRow("diagnostics", key, value);
  405. }
  406. },
  407. javaScript: function javaScript(data) {
  408. $("javascript-incremental-gc").textContent = data.incrementalGCEnabled;
  409. },
  410. accessibility: function accessibility(data) {
  411. $("a11y-activated").textContent = data.isActive;
  412. $("a11y-force-disabled").textContent = data.forceDisabled || 0;
  413. },
  414. libraryVersions: function libraryVersions(data) {
  415. let strings = stringBundle();
  416. let trs = [
  417. $.new("tr", [
  418. $.new("th", ""),
  419. $.new("th", strings.GetStringFromName("minLibVersions")),
  420. $.new("th", strings.GetStringFromName("loadedLibVersions")),
  421. ])
  422. ];
  423. sortedArrayFromObject(data).forEach(
  424. function ([name, val]) {
  425. trs.push($.new("tr", [
  426. $.new("td", name),
  427. $.new("td", val.minVersion),
  428. $.new("td", val.version),
  429. ]));
  430. }
  431. );
  432. $.append($("libversions-tbody"), trs);
  433. },
  434. userJS: function userJS(data) {
  435. if (!data.exists)
  436. return;
  437. let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
  438. userJSFile.append("user.js");
  439. $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
  440. $("prefs-user-js-section").style.display = "";
  441. // Clear the no-copy class
  442. $("prefs-user-js-section").className = "";
  443. }
  444. };
  445. var $ = document.getElementById.bind(document);
  446. $.new = function $_new(tag, textContentOrChildren, className, attributes) {
  447. let elt = document.createElement(tag);
  448. if (className)
  449. elt.className = className;
  450. if (attributes) {
  451. for (let attrName in attributes)
  452. elt.setAttribute(attrName, attributes[attrName]);
  453. }
  454. if (Array.isArray(textContentOrChildren))
  455. this.append(elt, textContentOrChildren);
  456. else
  457. elt.textContent = String(textContentOrChildren);
  458. return elt;
  459. };
  460. $.append = function $_append(parent, children) {
  461. children.forEach(c => parent.appendChild(c));
  462. };
  463. function stringBundle() {
  464. return Services.strings.createBundle(
  465. "chrome://global/locale/aboutSupport.properties");
  466. }
  467. function assembleFromGraphicsFailure(i, data)
  468. {
  469. // Only cover the cases we have today; for example, we do not have
  470. // log failures that assert and we assume the log level is 1/error.
  471. let message = data.failures[i];
  472. let index = data.indices[i];
  473. let what = "";
  474. if (message.search(/\[GFX1-\]: \(LF\)/) == 0) {
  475. // Non-asserting log failure - the message is substring(14)
  476. what = "LogFailure";
  477. message = message.substring(14);
  478. } else if (message.search(/\[GFX1-\]: /) == 0) {
  479. // Non-asserting - the message is substring(9)
  480. what = "Error";
  481. message = message.substring(9);
  482. } else if (message.search(/\[GFX1\]: /) == 0) {
  483. // Asserting - the message is substring(8)
  484. what = "Assert";
  485. message = message.substring(8);
  486. }
  487. let assembled = {"index" : index,
  488. "header" : ("(#" + index + ") " + what),
  489. "message" : message};
  490. return assembled;
  491. }
  492. function sortedArrayFromObject(obj) {
  493. let tuples = [];
  494. for (let prop in obj)
  495. tuples.push([prop, obj[prop]]);
  496. tuples.sort(([prop1, v1], [prop2, v2]) => prop1.localeCompare(prop2));
  497. return tuples;
  498. }
  499. function getLoadContext() {
  500. return window.QueryInterface(Ci.nsIInterfaceRequestor)
  501. .getInterface(Ci.nsIWebNavigation)
  502. .QueryInterface(Ci.nsILoadContext);
  503. }
  504. function copyContentsToClipboard() {
  505. // Get the HTML and text representations for the important part of the page.
  506. let contentsDiv = $("contents");
  507. let dataHtml = contentsDiv.innerHTML;
  508. let dataText = createTextForElement(contentsDiv);
  509. // We can't use plain strings, we have to use nsSupportsString.
  510. let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
  511. let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
  512. let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);
  513. let transferable = Cc["@mozilla.org/widget/transferable;1"]
  514. .createInstance(Ci.nsITransferable);
  515. transferable.init(getLoadContext());
  516. // Add the HTML flavor.
  517. transferable.addDataFlavor("text/html");
  518. ssHtml.data = dataHtml;
  519. transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2);
  520. // Add the plain text flavor.
  521. transferable.addDataFlavor("text/unicode");
  522. ssText.data = dataText;
  523. transferable.setTransferData("text/unicode", ssText, dataText.length * 2);
  524. // Store the data into the clipboard.
  525. let clipboard = Cc["@mozilla.org/widget/clipboard;1"]
  526. .getService(Ci.nsIClipboard);
  527. clipboard.setData(transferable, null, clipboard.kGlobalClipboard);
  528. }
  529. // Return the plain text representation of an element. Do a little bit
  530. // of pretty-printing to make it human-readable.
  531. function createTextForElement(elem) {
  532. let serializer = new Serializer();
  533. let text = serializer.serialize(elem);
  534. #ifdef XP_WIN
  535. // Actual CR/LF pairs are needed for some Windows text editors.
  536. text = text.replace(/\n/g, "\r\n");
  537. #endif
  538. return text;
  539. }
  540. function Serializer() {
  541. }
  542. Serializer.prototype = {
  543. serialize: function (rootElem) {
  544. this._lines = [];
  545. this._startNewLine();
  546. this._serializeElement(rootElem);
  547. this._startNewLine();
  548. return this._lines.join("\n").trim() + "\n";
  549. },
  550. // The current line is always the line that writing will start at next. When
  551. // an element is serialized, the current line is updated to be the line at
  552. // which the next element should be written.
  553. get _currentLine() {
  554. return this._lines.length ? this._lines[this._lines.length - 1] : null;
  555. },
  556. set _currentLine(val) {
  557. return this._lines[this._lines.length - 1] = val;
  558. },
  559. _serializeElement: function (elem) {
  560. if (this._ignoreElement(elem))
  561. return;
  562. // table
  563. if (elem.localName == "table") {
  564. this._serializeTable(elem);
  565. return;
  566. }
  567. // all other elements
  568. let hasText = false;
  569. for (let child of elem.childNodes) {
  570. if (child.nodeType == Node.TEXT_NODE) {
  571. let text = this._nodeText(
  572. child, (child.classList && child.classList.contains("endline")));
  573. this._appendText(text);
  574. hasText = hasText || !!text.trim();
  575. }
  576. else if (child.nodeType == Node.ELEMENT_NODE)
  577. this._serializeElement(child);
  578. }
  579. // For headings, draw a "line" underneath them so they stand out.
  580. if (/^h[0-9]+$/.test(elem.localName)) {
  581. let headerText = (this._currentLine || "").trim();
  582. if (headerText) {
  583. this._startNewLine();
  584. this._appendText("-".repeat(headerText.length));
  585. }
  586. }
  587. // Add a blank line underneath block elements but only if they contain text.
  588. if (hasText) {
  589. let display = window.getComputedStyle(elem).getPropertyValue("display");
  590. if (display == "block") {
  591. this._startNewLine();
  592. this._startNewLine();
  593. }
  594. }
  595. },
  596. _startNewLine: function () {
  597. let currLine = this._currentLine;
  598. if (currLine) {
  599. // The current line is not empty. Trim it.
  600. this._currentLine = currLine.trim();
  601. if (!this._currentLine)
  602. // The current line became empty. Discard it.
  603. this._lines.pop();
  604. }
  605. this._lines.push("");
  606. },
  607. _appendText: function (text) {
  608. this._currentLine += text;
  609. },
  610. _isHiddenSubHeading: function (th) {
  611. return th.parentNode.parentNode.style.display == "none";
  612. },
  613. _serializeTable: function (table) {
  614. // Collect the table's column headings if in fact there are any. First
  615. // check thead. If there's no thead, check the first tr.
  616. let colHeadings = {};
  617. let tableHeadingElem = table.querySelector("thead");
  618. if (!tableHeadingElem)
  619. tableHeadingElem = table.querySelector("tr");
  620. if (tableHeadingElem) {
  621. let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td");
  622. // If there's a contiguous run of th's in the children starting from the
  623. // rightmost child, then consider them to be column headings.
  624. for (let i = tableHeadingCols.length - 1; i >= 0; i--) {
  625. let col = tableHeadingCols[i];
  626. if (col.localName != "th" || col.classList.contains("title-column"))
  627. break;
  628. colHeadings[i] = this._nodeText(
  629. col, (col.classList && col.classList.contains("endline"))).trim();
  630. }
  631. }
  632. let hasColHeadings = Object.keys(colHeadings).length > 0;
  633. if (!hasColHeadings)
  634. tableHeadingElem = null;
  635. let trs = table.querySelectorAll("table > tr, tbody > tr");
  636. let startRow =
  637. tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0;
  638. if (startRow >= trs.length)
  639. // The table's empty.
  640. return;
  641. if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) {
  642. // Use column headings. Print each tr as a multi-line chunk like:
  643. // Heading 1: Column 1 value
  644. // Heading 2: Column 2 value
  645. for (let i = startRow; i < trs.length; i++) {
  646. if (this._ignoreElement(trs[i]))
  647. continue;
  648. let children = trs[i].querySelectorAll("td");
  649. for (let j = 0; j < children.length; j++) {
  650. let text = "";
  651. if (colHeadings[j])
  652. text += colHeadings[j] + ": ";
  653. text += this._nodeText(
  654. children[j],
  655. (children[j].classList &&
  656. children[j].classList.contains("endline"))).trim();
  657. this._appendText(text);
  658. this._startNewLine();
  659. }
  660. this._startNewLine();
  661. }
  662. return;
  663. }
  664. // Don't use column headings. Assume the table has only two columns and
  665. // print each tr in a single line like:
  666. // Column 1 value: Column 2 value
  667. for (let i = startRow; i < trs.length; i++) {
  668. if (this._ignoreElement(trs[i]))
  669. continue;
  670. let children = trs[i].querySelectorAll("th,td");
  671. let rowHeading = this._nodeText(
  672. children[0],
  673. (children[0].classList &&
  674. children[0].classList.contains("endline"))).trim();
  675. if (children[0].classList.contains("title-column")) {
  676. if (!this._isHiddenSubHeading(children[0]))
  677. this._appendText(rowHeading);
  678. } else if (children.length == 1) {
  679. // This is a single-cell row.
  680. this._appendText(rowHeading);
  681. } else {
  682. let childTables = trs[i].querySelectorAll("table");
  683. if (childTables.length) {
  684. // If we have child tables, don't use nodeText - its trs are already
  685. // queued up from querySelectorAll earlier.
  686. this._appendText(rowHeading + ": ");
  687. } else {
  688. this._appendText(rowHeading + ": " + this._nodeText(
  689. children[1],
  690. (children[1].classList &&
  691. children[1].classList.contains("endline"))).trim());
  692. }
  693. }
  694. this._startNewLine();
  695. }
  696. this._startNewLine();
  697. },
  698. _ignoreElement: function (elem) {
  699. return elem.classList.contains("no-copy");
  700. },
  701. _nodeText: function (node, endline) {
  702. let whiteChars = /\s+/g
  703. let whiteCharsButNoEndline = /(?!\n)[\s]+/g;
  704. let _node = node.cloneNode(true);
  705. if (_node.firstElementChild &&
  706. (_node.firstElementChild.nodeName.toLowerCase() == "button")) {
  707. _node.removeChild(_node.firstElementChild);
  708. }
  709. return _node.textContent.replace(
  710. endline ? whiteCharsButNoEndline : whiteChars, " ");
  711. },
  712. };
  713. function openProfileDirectory() {
  714. // Get the profile directory.
  715. let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
  716. let profileDir = currProfD.path;
  717. // Show the profile directory.
  718. let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
  719. "nsILocalFile", "initWithPath");
  720. new nsLocalFile(profileDir).reveal();
  721. }
  722. /**
  723. * Profile reset is only supported for the default profile if the appropriate migrator exists.
  724. */
  725. function populateActionBox() {
  726. if (ResetProfile.resetSupported()) {
  727. $("reset-box").style.display = "block";
  728. $("action-box").style.display = "block";
  729. }
  730. if (!Services.appinfo.inSafeMode) {
  731. $("safe-mode-box").style.display = "block";
  732. $("action-box").style.display = "block";
  733. }
  734. }
  735. // Prompt user to restart the browser
  736. function restart(safeMode) {
  737. let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
  738. .createInstance(Ci.nsISupportsPRBool);
  739. Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
  740. if (cancelQuit.data) {
  741. return;
  742. }
  743. let flags = Ci.nsIAppStartup.eAttemptQuit;
  744. if (safeMode) {
  745. Services.startup.restartInSafeMode(flags);
  746. } else {
  747. Services.startup.quit(flags | Ci.nsIAppStartup.eRestart);
  748. }
  749. }
  750. /**
  751. * Set up event listeners for buttons.
  752. */
  753. function setupEventListeners() {
  754. #ifdef MOZ_UPDATER
  755. $("show-update-history-button").addEventListener("click", function(event) {
  756. var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
  757. prompter.showUpdateHistory(window);
  758. });
  759. #endif
  760. $("reset-box-button").addEventListener("click", function(event) {
  761. ResetProfile.openConfirmationDialog(window);
  762. });
  763. $("copy-to-clipboard").addEventListener("click", function(event) {
  764. copyContentsToClipboard();
  765. });
  766. $("profile-dir-button").addEventListener("click", function(event) {
  767. openProfileDirectory();
  768. });
  769. $("restart-in-safe-mode-button").addEventListener("click", function(event) {
  770. if (Services.obs.enumerateObservers("restart-in-safe-mode").hasMoreElements()) {
  771. Services.obs.notifyObservers(null, "restart-in-safe-mode", "");
  772. } else {
  773. restart(true);
  774. }
  775. });
  776. $("restart-button").addEventListener("click", function(event) {
  777. restart(false);
  778. });
  779. $("verify-place-integrity-button").addEventListener("click", function(event) {
  780. PlacesDBUtils.checkAndFixDatabase(function(aLog) {
  781. let msg = aLog.join("\n");
  782. $("verify-place-result").style.display = "block";
  783. $("verify-place-result-parent").classList.remove("no-copy");
  784. $("verify-place-result").textContent = msg;
  785. });
  786. });
  787. }