toolkit/content/aboutSupport.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
michael@0 6
michael@0 7 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 8 Cu.import("resource://gre/modules/Services.jsm");
michael@0 9 Cu.import("resource://gre/modules/Troubleshoot.jsm");
michael@0 10 Cu.import("resource://gre/modules/ResetProfile.jsm");
michael@0 11
michael@0 12 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
michael@0 13 "resource://gre/modules/PluralForm.jsm");
michael@0 14
michael@0 15 window.addEventListener("load", function onload(event) {
michael@0 16 try {
michael@0 17 window.removeEventListener("load", onload, false);
michael@0 18 Troubleshoot.snapshot(function (snapshot) {
michael@0 19 for (let prop in snapshotFormatters)
michael@0 20 snapshotFormatters[prop](snapshot[prop]);
michael@0 21 });
michael@0 22 populateResetBox();
michael@0 23 setupEventListeners();
michael@0 24 } catch (e) {
michael@0 25 Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack);
michael@0 26 }
michael@0 27 }, false);
michael@0 28
michael@0 29 // Each property in this object corresponds to a property in Troubleshoot.jsm's
michael@0 30 // snapshot data. Each function is passed its property's corresponding data,
michael@0 31 // and it's the function's job to update the page with it.
michael@0 32 let snapshotFormatters = {
michael@0 33
michael@0 34 application: function application(data) {
michael@0 35 $("application-box").textContent = data.name;
michael@0 36 $("useragent-box").textContent = data.userAgent;
michael@0 37 $("supportLink").href = data.supportURL;
michael@0 38 let version = data.version;
michael@0 39 if (data.vendor)
michael@0 40 version += " (" + data.vendor + ")";
michael@0 41 $("version-box").textContent = version;
michael@0 42 },
michael@0 43
michael@0 44 #ifdef MOZ_CRASHREPORTER
michael@0 45 crashes: function crashes(data) {
michael@0 46 let strings = stringBundle();
michael@0 47 let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
michael@0 48 $("crashes-title").textContent =
michael@0 49 PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle"))
michael@0 50 .replace("#1", daysRange);
michael@0 51 let reportURL;
michael@0 52 try {
michael@0 53 reportURL = Services.prefs.getCharPref("breakpad.reportURL");
michael@0 54 // Ignore any non http/https urls
michael@0 55 if (!/^https?:/i.test(reportURL))
michael@0 56 reportURL = null;
michael@0 57 }
michael@0 58 catch (e) { }
michael@0 59 if (!reportURL) {
michael@0 60 $("crashes-noConfig").style.display = "block";
michael@0 61 $("crashes-noConfig").classList.remove("no-copy");
michael@0 62 return;
michael@0 63 }
michael@0 64 else {
michael@0 65 $("crashes-allReports").style.display = "block";
michael@0 66 $("crashes-allReports").classList.remove("no-copy");
michael@0 67 }
michael@0 68
michael@0 69 if (data.pending > 0) {
michael@0 70 $("crashes-allReportsWithPending").textContent =
michael@0 71 PluralForm.get(data.pending, strings.GetStringFromName("pendingReports"))
michael@0 72 .replace("#1", data.pending);
michael@0 73 }
michael@0 74
michael@0 75 let dateNow = new Date();
michael@0 76 $.append($("crashes-tbody"), data.submitted.map(function (crash) {
michael@0 77 let date = new Date(crash.date);
michael@0 78 let timePassed = dateNow - date;
michael@0 79 let formattedDate;
michael@0 80 if (timePassed >= 24 * 60 * 60 * 1000)
michael@0 81 {
michael@0 82 let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000));
michael@0 83 let daysPassedString = strings.GetStringFromName("crashesTimeDays");
michael@0 84 formattedDate = PluralForm.get(daysPassed, daysPassedString)
michael@0 85 .replace("#1", daysPassed);
michael@0 86 }
michael@0 87 else if (timePassed >= 60 * 60 * 1000)
michael@0 88 {
michael@0 89 let hoursPassed = Math.round(timePassed / (60 * 60 * 1000));
michael@0 90 let hoursPassedString = strings.GetStringFromName("crashesTimeHours");
michael@0 91 formattedDate = PluralForm.get(hoursPassed, hoursPassedString)
michael@0 92 .replace("#1", hoursPassed);
michael@0 93 }
michael@0 94 else
michael@0 95 {
michael@0 96 let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1);
michael@0 97 let minutesPassedString = strings.GetStringFromName("crashesTimeMinutes");
michael@0 98 formattedDate = PluralForm.get(minutesPassed, minutesPassedString)
michael@0 99 .replace("#1", minutesPassed);
michael@0 100 }
michael@0 101 return $.new("tr", [
michael@0 102 $.new("td", [
michael@0 103 $.new("a", crash.id, null, {href : reportURL + crash.id})
michael@0 104 ]),
michael@0 105 $.new("td", formattedDate)
michael@0 106 ]);
michael@0 107 }));
michael@0 108 },
michael@0 109 #endif
michael@0 110
michael@0 111 extensions: function extensions(data) {
michael@0 112 $.append($("extensions-tbody"), data.map(function (extension) {
michael@0 113 return $.new("tr", [
michael@0 114 $.new("td", extension.name),
michael@0 115 $.new("td", extension.version),
michael@0 116 $.new("td", extension.isActive),
michael@0 117 $.new("td", extension.id),
michael@0 118 ]);
michael@0 119 }));
michael@0 120 },
michael@0 121
michael@0 122 experiments: function experiments(data) {
michael@0 123 $.append($("experiments-tbody"), data.map(function (experiment) {
michael@0 124 return $.new("tr", [
michael@0 125 $.new("td", experiment.name),
michael@0 126 $.new("td", experiment.id),
michael@0 127 $.new("td", experiment.description),
michael@0 128 $.new("td", experiment.active),
michael@0 129 $.new("td", experiment.endDate),
michael@0 130 $.new("td", [
michael@0 131 $.new("a", experiment.detailURL, null, {href : experiment.detailURL,})
michael@0 132 ]),
michael@0 133 ]);
michael@0 134 }));
michael@0 135 },
michael@0 136
michael@0 137 modifiedPreferences: function modifiedPreferences(data) {
michael@0 138 $.append($("prefs-tbody"), sortedArrayFromObject(data).map(
michael@0 139 function ([name, value]) {
michael@0 140 return $.new("tr", [
michael@0 141 $.new("td", name, "pref-name"),
michael@0 142 // Very long preference values can cause users problems when they
michael@0 143 // copy and paste them into some text editors. Long values generally
michael@0 144 // aren't useful anyway, so truncate them to a reasonable length.
michael@0 145 $.new("td", String(value).substr(0, 120), "pref-value"),
michael@0 146 ]);
michael@0 147 }
michael@0 148 ));
michael@0 149 },
michael@0 150
michael@0 151 graphics: function graphics(data) {
michael@0 152 // graphics-info-properties tbody
michael@0 153 if ("info" in data) {
michael@0 154 let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) {
michael@0 155 return $.new("tr", [
michael@0 156 $.new("th", prop, "column"),
michael@0 157 $.new("td", String(val)),
michael@0 158 ]);
michael@0 159 });
michael@0 160 $.append($("graphics-info-properties"), trs);
michael@0 161 delete data.info;
michael@0 162 }
michael@0 163
michael@0 164 // graphics-failures-tbody tbody
michael@0 165 if ("failures" in data) {
michael@0 166 $.append($("graphics-failures-tbody"), data.failures.map(function (val) {
michael@0 167 return $.new("tr", [$.new("td", val)]);
michael@0 168 }));
michael@0 169 delete data.failures;
michael@0 170 }
michael@0 171
michael@0 172 // graphics-tbody tbody
michael@0 173
michael@0 174 function localizedMsg(msgArray) {
michael@0 175 let nameOrMsg = msgArray.shift();
michael@0 176 if (msgArray.length) {
michael@0 177 // formatStringFromName logs an NS_ASSERTION failure otherwise that says
michael@0 178 // "use GetStringFromName". Lame.
michael@0 179 try {
michael@0 180 return strings.formatStringFromName(nameOrMsg, msgArray,
michael@0 181 msgArray.length);
michael@0 182 }
michael@0 183 catch (err) {
michael@0 184 // Throws if nameOrMsg is not a name in the bundle. This shouldn't
michael@0 185 // actually happen though, since msgArray.length > 1 => nameOrMsg is a
michael@0 186 // name in the bundle, not a message, and the remaining msgArray
michael@0 187 // elements are parameters.
michael@0 188 return nameOrMsg;
michael@0 189 }
michael@0 190 }
michael@0 191 try {
michael@0 192 return strings.GetStringFromName(nameOrMsg);
michael@0 193 }
michael@0 194 catch (err) {
michael@0 195 // Throws if nameOrMsg is not a name in the bundle.
michael@0 196 }
michael@0 197 return nameOrMsg;
michael@0 198 }
michael@0 199
michael@0 200 let out = Object.create(data);
michael@0 201 let strings = stringBundle();
michael@0 202
michael@0 203 out.acceleratedWindows =
michael@0 204 data.numAcceleratedWindows + "/" + data.numTotalWindows;
michael@0 205 if (data.windowLayerManagerType)
michael@0 206 out.acceleratedWindows += " " + data.windowLayerManagerType;
michael@0 207 if (data.windowLayerManagerRemote)
michael@0 208 out.acceleratedWindows += " (OMTC)";
michael@0 209 if (data.numAcceleratedWindowsMessage)
michael@0 210 out.acceleratedWindows +=
michael@0 211 " " + localizedMsg(data.numAcceleratedWindowsMessage);
michael@0 212 delete data.numAcceleratedWindows;
michael@0 213 delete data.numTotalWindows;
michael@0 214 delete data.windowLayerManagerType;
michael@0 215 delete data.numAcceleratedWindowsMessage;
michael@0 216
michael@0 217 if ("direct2DEnabledMessage" in data) {
michael@0 218 out.direct2DEnabled = localizedMsg(data.direct2DEnabledMessage);
michael@0 219 delete data.direct2DEnabledMessage;
michael@0 220 delete data.direct2DEnabled;
michael@0 221 }
michael@0 222
michael@0 223 if ("directWriteEnabled" in data) {
michael@0 224 out.directWriteEnabled = data.directWriteEnabled;
michael@0 225 if ("directWriteVersion" in data)
michael@0 226 out.directWriteEnabled += " (" + data.directWriteVersion + ")";
michael@0 227 delete data.directWriteEnabled;
michael@0 228 delete data.directWriteVersion;
michael@0 229 }
michael@0 230
michael@0 231 if ("webglRendererMessage" in data) {
michael@0 232 out.webglRenderer = localizedMsg(data.webglRendererMessage);
michael@0 233 delete data.webglRendererMessage;
michael@0 234 delete data.webglRenderer;
michael@0 235 }
michael@0 236
michael@0 237 let localizedOut = {};
michael@0 238 for (let prop in out) {
michael@0 239 let val = out[prop];
michael@0 240 if (typeof(val) == "string" && !val)
michael@0 241 // Ignore properties that are empty strings.
michael@0 242 continue;
michael@0 243 try {
michael@0 244 var localizedName = strings.GetStringFromName(prop);
michael@0 245 }
michael@0 246 catch (err) {
michael@0 247 // This shouldn't happen, but if there's a reported graphics property
michael@0 248 // that isn't in the string bundle, don't let it break the page.
michael@0 249 localizedName = prop;
michael@0 250 }
michael@0 251 localizedOut[localizedName] = val;
michael@0 252 }
michael@0 253 let trs = sortedArrayFromObject(localizedOut).map(function ([prop, val]) {
michael@0 254 return $.new("tr", [
michael@0 255 $.new("th", prop, "column"),
michael@0 256 $.new("td", val),
michael@0 257 ]);
michael@0 258 });
michael@0 259 $.append($("graphics-tbody"), trs);
michael@0 260 },
michael@0 261
michael@0 262 javaScript: function javaScript(data) {
michael@0 263 $("javascript-incremental-gc").textContent = data.incrementalGCEnabled;
michael@0 264 },
michael@0 265
michael@0 266 accessibility: function accessibility(data) {
michael@0 267 $("a11y-activated").textContent = data.isActive;
michael@0 268 $("a11y-force-disabled").textContent = data.forceDisabled || 0;
michael@0 269 },
michael@0 270
michael@0 271 libraryVersions: function libraryVersions(data) {
michael@0 272 let strings = stringBundle();
michael@0 273 let trs = [
michael@0 274 $.new("tr", [
michael@0 275 $.new("th", ""),
michael@0 276 $.new("th", strings.GetStringFromName("minLibVersions")),
michael@0 277 $.new("th", strings.GetStringFromName("loadedLibVersions")),
michael@0 278 ])
michael@0 279 ];
michael@0 280 sortedArrayFromObject(data).forEach(
michael@0 281 function ([name, val]) {
michael@0 282 trs.push($.new("tr", [
michael@0 283 $.new("td", name),
michael@0 284 $.new("td", val.minVersion),
michael@0 285 $.new("td", val.version),
michael@0 286 ]));
michael@0 287 }
michael@0 288 );
michael@0 289 $.append($("libversions-tbody"), trs);
michael@0 290 },
michael@0 291
michael@0 292 userJS: function userJS(data) {
michael@0 293 if (!data.exists)
michael@0 294 return;
michael@0 295 let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
michael@0 296 userJSFile.append("user.js");
michael@0 297 $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
michael@0 298 $("prefs-user-js-section").style.display = "";
michael@0 299 // Clear the no-copy class
michael@0 300 $("prefs-user-js-section").className = "";
michael@0 301 },
michael@0 302 };
michael@0 303
michael@0 304 let $ = document.getElementById.bind(document);
michael@0 305
michael@0 306 $.new = function $_new(tag, textContentOrChildren, className, attributes) {
michael@0 307 let elt = document.createElement(tag);
michael@0 308 if (className)
michael@0 309 elt.className = className;
michael@0 310 if (attributes) {
michael@0 311 for (let attrName in attributes)
michael@0 312 elt.setAttribute(attrName, attributes[attrName]);
michael@0 313 }
michael@0 314 if (Array.isArray(textContentOrChildren))
michael@0 315 this.append(elt, textContentOrChildren);
michael@0 316 else
michael@0 317 elt.textContent = String(textContentOrChildren);
michael@0 318 return elt;
michael@0 319 };
michael@0 320
michael@0 321 $.append = function $_append(parent, children) {
michael@0 322 children.forEach(function (c) parent.appendChild(c));
michael@0 323 };
michael@0 324
michael@0 325 function stringBundle() {
michael@0 326 return Services.strings.createBundle(
michael@0 327 "chrome://global/locale/aboutSupport.properties");
michael@0 328 }
michael@0 329
michael@0 330 function sortedArrayFromObject(obj) {
michael@0 331 let tuples = [];
michael@0 332 for (let prop in obj)
michael@0 333 tuples.push([prop, obj[prop]]);
michael@0 334 tuples.sort(function ([prop1, v1], [prop2, v2]) prop1.localeCompare(prop2));
michael@0 335 return tuples;
michael@0 336 }
michael@0 337
michael@0 338 function copyRawDataToClipboard(button) {
michael@0 339 if (button)
michael@0 340 button.disabled = true;
michael@0 341 try {
michael@0 342 Troubleshoot.snapshot(function (snapshot) {
michael@0 343 if (button)
michael@0 344 button.disabled = false;
michael@0 345 let str = Cc["@mozilla.org/supports-string;1"].
michael@0 346 createInstance(Ci.nsISupportsString);
michael@0 347 str.data = JSON.stringify(snapshot, undefined, 2);
michael@0 348 let transferable = Cc["@mozilla.org/widget/transferable;1"].
michael@0 349 createInstance(Ci.nsITransferable);
michael@0 350 transferable.init(getLoadContext());
michael@0 351 transferable.addDataFlavor("text/unicode");
michael@0 352 transferable.setTransferData("text/unicode", str, str.data.length * 2);
michael@0 353 Cc["@mozilla.org/widget/clipboard;1"].
michael@0 354 getService(Ci.nsIClipboard).
michael@0 355 setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
michael@0 356 #ifdef ANDROID
michael@0 357 // Present a toast notification.
michael@0 358 let message = {
michael@0 359 type: "Toast:Show",
michael@0 360 message: stringBundle().GetStringFromName("rawDataCopied"),
michael@0 361 duration: "short"
michael@0 362 };
michael@0 363 Services.androidBridge.handleGeckoMessage(message);
michael@0 364 #endif
michael@0 365 });
michael@0 366 }
michael@0 367 catch (err) {
michael@0 368 if (button)
michael@0 369 button.disabled = false;
michael@0 370 throw err;
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 function getLoadContext() {
michael@0 375 return window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 376 .getInterface(Ci.nsIWebNavigation)
michael@0 377 .QueryInterface(Ci.nsILoadContext);
michael@0 378 }
michael@0 379
michael@0 380 function copyContentsToClipboard() {
michael@0 381 // Get the HTML and text representations for the important part of the page.
michael@0 382 let contentsDiv = $("contents");
michael@0 383 let dataHtml = contentsDiv.innerHTML;
michael@0 384 let dataText = createTextForElement(contentsDiv);
michael@0 385
michael@0 386 // We can't use plain strings, we have to use nsSupportsString.
michael@0 387 let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
michael@0 388 let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
michael@0 389 let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);
michael@0 390
michael@0 391 let transferable = Cc["@mozilla.org/widget/transferable;1"]
michael@0 392 .createInstance(Ci.nsITransferable);
michael@0 393 transferable.init(getLoadContext());
michael@0 394
michael@0 395 // Add the HTML flavor.
michael@0 396 transferable.addDataFlavor("text/html");
michael@0 397 ssHtml.data = dataHtml;
michael@0 398 transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2);
michael@0 399
michael@0 400 // Add the plain text flavor.
michael@0 401 transferable.addDataFlavor("text/unicode");
michael@0 402 ssText.data = dataText;
michael@0 403 transferable.setTransferData("text/unicode", ssText, dataText.length * 2);
michael@0 404
michael@0 405 // Store the data into the clipboard.
michael@0 406 let clipboard = Cc["@mozilla.org/widget/clipboard;1"]
michael@0 407 .getService(Ci.nsIClipboard);
michael@0 408 clipboard.setData(transferable, null, clipboard.kGlobalClipboard);
michael@0 409
michael@0 410 #ifdef ANDROID
michael@0 411 // Present a toast notification.
michael@0 412 let message = {
michael@0 413 type: "Toast:Show",
michael@0 414 message: stringBundle().GetStringFromName("textCopied"),
michael@0 415 duration: "short"
michael@0 416 };
michael@0 417 Services.androidBridge.handleGeckoMessage(message);
michael@0 418 #endif
michael@0 419 }
michael@0 420
michael@0 421 // Return the plain text representation of an element. Do a little bit
michael@0 422 // of pretty-printing to make it human-readable.
michael@0 423 function createTextForElement(elem) {
michael@0 424 let serializer = new Serializer();
michael@0 425 let text = serializer.serialize(elem);
michael@0 426
michael@0 427 // Actual CR/LF pairs are needed for some Windows text editors.
michael@0 428 #ifdef XP_WIN
michael@0 429 text = text.replace(/\n/g, "\r\n");
michael@0 430 #endif
michael@0 431
michael@0 432 return text;
michael@0 433 }
michael@0 434
michael@0 435 function Serializer() {
michael@0 436 }
michael@0 437
michael@0 438 Serializer.prototype = {
michael@0 439
michael@0 440 serialize: function (rootElem) {
michael@0 441 this._lines = [];
michael@0 442 this._startNewLine();
michael@0 443 this._serializeElement(rootElem);
michael@0 444 this._startNewLine();
michael@0 445 return this._lines.join("\n").trim() + "\n";
michael@0 446 },
michael@0 447
michael@0 448 // The current line is always the line that writing will start at next. When
michael@0 449 // an element is serialized, the current line is updated to be the line at
michael@0 450 // which the next element should be written.
michael@0 451 get _currentLine() {
michael@0 452 return this._lines.length ? this._lines[this._lines.length - 1] : null;
michael@0 453 },
michael@0 454
michael@0 455 set _currentLine(val) {
michael@0 456 return this._lines[this._lines.length - 1] = val;
michael@0 457 },
michael@0 458
michael@0 459 _serializeElement: function (elem) {
michael@0 460 if (this._ignoreElement(elem))
michael@0 461 return;
michael@0 462
michael@0 463 // table
michael@0 464 if (elem.localName == "table") {
michael@0 465 this._serializeTable(elem);
michael@0 466 return;
michael@0 467 }
michael@0 468
michael@0 469 // all other elements
michael@0 470
michael@0 471 let hasText = false;
michael@0 472 for (let child of elem.childNodes) {
michael@0 473 if (child.nodeType == Node.TEXT_NODE) {
michael@0 474 let text = this._nodeText(child);
michael@0 475 this._appendText(text);
michael@0 476 hasText = hasText || !!text.trim();
michael@0 477 }
michael@0 478 else if (child.nodeType == Node.ELEMENT_NODE)
michael@0 479 this._serializeElement(child);
michael@0 480 }
michael@0 481
michael@0 482 // For headings, draw a "line" underneath them so they stand out.
michael@0 483 if (/^h[0-9]+$/.test(elem.localName)) {
michael@0 484 let headerText = (this._currentLine || "").trim();
michael@0 485 if (headerText) {
michael@0 486 this._startNewLine();
michael@0 487 this._appendText("-".repeat(headerText.length));
michael@0 488 }
michael@0 489 }
michael@0 490
michael@0 491 // Add a blank line underneath block elements but only if they contain text.
michael@0 492 if (hasText) {
michael@0 493 let display = window.getComputedStyle(elem).getPropertyValue("display");
michael@0 494 if (display == "block") {
michael@0 495 this._startNewLine();
michael@0 496 this._startNewLine();
michael@0 497 }
michael@0 498 }
michael@0 499 },
michael@0 500
michael@0 501 _startNewLine: function (lines) {
michael@0 502 let currLine = this._currentLine;
michael@0 503 if (currLine) {
michael@0 504 // The current line is not empty. Trim it.
michael@0 505 this._currentLine = currLine.trim();
michael@0 506 if (!this._currentLine)
michael@0 507 // The current line became empty. Discard it.
michael@0 508 this._lines.pop();
michael@0 509 }
michael@0 510 this._lines.push("");
michael@0 511 },
michael@0 512
michael@0 513 _appendText: function (text, lines) {
michael@0 514 this._currentLine += text;
michael@0 515 },
michael@0 516
michael@0 517 _serializeTable: function (table) {
michael@0 518 // Collect the table's column headings if in fact there are any. First
michael@0 519 // check thead. If there's no thead, check the first tr.
michael@0 520 let colHeadings = {};
michael@0 521 let tableHeadingElem = table.querySelector("thead");
michael@0 522 if (!tableHeadingElem)
michael@0 523 tableHeadingElem = table.querySelector("tr");
michael@0 524 if (tableHeadingElem) {
michael@0 525 let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td");
michael@0 526 // If there's a contiguous run of th's in the children starting from the
michael@0 527 // rightmost child, then consider them to be column headings.
michael@0 528 for (let i = tableHeadingCols.length - 1; i >= 0; i--) {
michael@0 529 if (tableHeadingCols[i].localName != "th")
michael@0 530 break;
michael@0 531 colHeadings[i] = this._nodeText(tableHeadingCols[i]).trim();
michael@0 532 }
michael@0 533 }
michael@0 534 let hasColHeadings = Object.keys(colHeadings).length > 0;
michael@0 535 if (!hasColHeadings)
michael@0 536 tableHeadingElem = null;
michael@0 537
michael@0 538 let trs = table.querySelectorAll("table > tr, tbody > tr");
michael@0 539 let startRow =
michael@0 540 tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0;
michael@0 541
michael@0 542 if (startRow >= trs.length)
michael@0 543 // The table's empty.
michael@0 544 return;
michael@0 545
michael@0 546 if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) {
michael@0 547 // Use column headings. Print each tr as a multi-line chunk like:
michael@0 548 // Heading 1: Column 1 value
michael@0 549 // Heading 2: Column 2 value
michael@0 550 for (let i = startRow; i < trs.length; i++) {
michael@0 551 if (this._ignoreElement(trs[i]))
michael@0 552 continue;
michael@0 553 let children = trs[i].querySelectorAll("td");
michael@0 554 for (let j = 0; j < children.length; j++) {
michael@0 555 let text = "";
michael@0 556 if (colHeadings[j])
michael@0 557 text += colHeadings[j] + ": ";
michael@0 558 text += this._nodeText(children[j]).trim();
michael@0 559 this._appendText(text);
michael@0 560 this._startNewLine();
michael@0 561 }
michael@0 562 this._startNewLine();
michael@0 563 }
michael@0 564 return;
michael@0 565 }
michael@0 566
michael@0 567 // Don't use column headings. Assume the table has only two columns and
michael@0 568 // print each tr in a single line like:
michael@0 569 // Column 1 value: Column 2 value
michael@0 570 for (let i = startRow; i < trs.length; i++) {
michael@0 571 if (this._ignoreElement(trs[i]))
michael@0 572 continue;
michael@0 573 let children = trs[i].querySelectorAll("th,td");
michael@0 574 let rowHeading = this._nodeText(children[0]).trim();
michael@0 575 this._appendText(rowHeading + ": " + this._nodeText(children[1]).trim());
michael@0 576 this._startNewLine();
michael@0 577 }
michael@0 578 this._startNewLine();
michael@0 579 },
michael@0 580
michael@0 581 _ignoreElement: function (elem) {
michael@0 582 return elem.classList.contains("no-copy");
michael@0 583 },
michael@0 584
michael@0 585 _nodeText: function (node) {
michael@0 586 return node.textContent.replace(/\s+/g, " ");
michael@0 587 },
michael@0 588 };
michael@0 589
michael@0 590 function openProfileDirectory() {
michael@0 591 // Get the profile directory.
michael@0 592 let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
michael@0 593 let profileDir = currProfD.path;
michael@0 594
michael@0 595 // Show the profile directory.
michael@0 596 let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
michael@0 597 "nsILocalFile", "initWithPath");
michael@0 598 new nsLocalFile(profileDir).reveal();
michael@0 599 }
michael@0 600
michael@0 601 /**
michael@0 602 * Profile reset is only supported for the default profile if the appropriate migrator exists.
michael@0 603 */
michael@0 604 function populateResetBox() {
michael@0 605 if (ResetProfile.resetSupported())
michael@0 606 $("reset-box").style.visibility = "visible";
michael@0 607 }
michael@0 608
michael@0 609 /**
michael@0 610 * Set up event listeners for buttons.
michael@0 611 */
michael@0 612 function setupEventListeners(){
michael@0 613 $("show-update-history-button").addEventListener("click", function (event) {
michael@0 614 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
michael@0 615 prompter.showUpdateHistory(window);
michael@0 616 });
michael@0 617 $("reset-box-button").addEventListener("click", function (event){
michael@0 618 ResetProfile.openConfirmationDialog(window);
michael@0 619 });
michael@0 620 $("copy-raw-data-to-clipboard").addEventListener("click", function (event){
michael@0 621 copyRawDataToClipboard(this);
michael@0 622 });
michael@0 623 $("copy-to-clipboard").addEventListener("click", function (event){
michael@0 624 copyContentsToClipboard();
michael@0 625 });
michael@0 626 $("profile-dir-button").addEventListener("click", function (event){
michael@0 627 openProfileDirectory();
michael@0 628 });
michael@0 629 }

mercurial