1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/content/aboutSupport.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,629 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; 1.9 + 1.10 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.11 +Cu.import("resource://gre/modules/Services.jsm"); 1.12 +Cu.import("resource://gre/modules/Troubleshoot.jsm"); 1.13 +Cu.import("resource://gre/modules/ResetProfile.jsm"); 1.14 + 1.15 +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", 1.16 + "resource://gre/modules/PluralForm.jsm"); 1.17 + 1.18 +window.addEventListener("load", function onload(event) { 1.19 + try { 1.20 + window.removeEventListener("load", onload, false); 1.21 + Troubleshoot.snapshot(function (snapshot) { 1.22 + for (let prop in snapshotFormatters) 1.23 + snapshotFormatters[prop](snapshot[prop]); 1.24 + }); 1.25 + populateResetBox(); 1.26 + setupEventListeners(); 1.27 + } catch (e) { 1.28 + Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack); 1.29 + } 1.30 +}, false); 1.31 + 1.32 +// Each property in this object corresponds to a property in Troubleshoot.jsm's 1.33 +// snapshot data. Each function is passed its property's corresponding data, 1.34 +// and it's the function's job to update the page with it. 1.35 +let snapshotFormatters = { 1.36 + 1.37 + application: function application(data) { 1.38 + $("application-box").textContent = data.name; 1.39 + $("useragent-box").textContent = data.userAgent; 1.40 + $("supportLink").href = data.supportURL; 1.41 + let version = data.version; 1.42 + if (data.vendor) 1.43 + version += " (" + data.vendor + ")"; 1.44 + $("version-box").textContent = version; 1.45 + }, 1.46 + 1.47 +#ifdef MOZ_CRASHREPORTER 1.48 + crashes: function crashes(data) { 1.49 + let strings = stringBundle(); 1.50 + let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000); 1.51 + $("crashes-title").textContent = 1.52 + PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle")) 1.53 + .replace("#1", daysRange); 1.54 + let reportURL; 1.55 + try { 1.56 + reportURL = Services.prefs.getCharPref("breakpad.reportURL"); 1.57 + // Ignore any non http/https urls 1.58 + if (!/^https?:/i.test(reportURL)) 1.59 + reportURL = null; 1.60 + } 1.61 + catch (e) { } 1.62 + if (!reportURL) { 1.63 + $("crashes-noConfig").style.display = "block"; 1.64 + $("crashes-noConfig").classList.remove("no-copy"); 1.65 + return; 1.66 + } 1.67 + else { 1.68 + $("crashes-allReports").style.display = "block"; 1.69 + $("crashes-allReports").classList.remove("no-copy"); 1.70 + } 1.71 + 1.72 + if (data.pending > 0) { 1.73 + $("crashes-allReportsWithPending").textContent = 1.74 + PluralForm.get(data.pending, strings.GetStringFromName("pendingReports")) 1.75 + .replace("#1", data.pending); 1.76 + } 1.77 + 1.78 + let dateNow = new Date(); 1.79 + $.append($("crashes-tbody"), data.submitted.map(function (crash) { 1.80 + let date = new Date(crash.date); 1.81 + let timePassed = dateNow - date; 1.82 + let formattedDate; 1.83 + if (timePassed >= 24 * 60 * 60 * 1000) 1.84 + { 1.85 + let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000)); 1.86 + let daysPassedString = strings.GetStringFromName("crashesTimeDays"); 1.87 + formattedDate = PluralForm.get(daysPassed, daysPassedString) 1.88 + .replace("#1", daysPassed); 1.89 + } 1.90 + else if (timePassed >= 60 * 60 * 1000) 1.91 + { 1.92 + let hoursPassed = Math.round(timePassed / (60 * 60 * 1000)); 1.93 + let hoursPassedString = strings.GetStringFromName("crashesTimeHours"); 1.94 + formattedDate = PluralForm.get(hoursPassed, hoursPassedString) 1.95 + .replace("#1", hoursPassed); 1.96 + } 1.97 + else 1.98 + { 1.99 + let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1); 1.100 + let minutesPassedString = strings.GetStringFromName("crashesTimeMinutes"); 1.101 + formattedDate = PluralForm.get(minutesPassed, minutesPassedString) 1.102 + .replace("#1", minutesPassed); 1.103 + } 1.104 + return $.new("tr", [ 1.105 + $.new("td", [ 1.106 + $.new("a", crash.id, null, {href : reportURL + crash.id}) 1.107 + ]), 1.108 + $.new("td", formattedDate) 1.109 + ]); 1.110 + })); 1.111 + }, 1.112 +#endif 1.113 + 1.114 + extensions: function extensions(data) { 1.115 + $.append($("extensions-tbody"), data.map(function (extension) { 1.116 + return $.new("tr", [ 1.117 + $.new("td", extension.name), 1.118 + $.new("td", extension.version), 1.119 + $.new("td", extension.isActive), 1.120 + $.new("td", extension.id), 1.121 + ]); 1.122 + })); 1.123 + }, 1.124 + 1.125 + experiments: function experiments(data) { 1.126 + $.append($("experiments-tbody"), data.map(function (experiment) { 1.127 + return $.new("tr", [ 1.128 + $.new("td", experiment.name), 1.129 + $.new("td", experiment.id), 1.130 + $.new("td", experiment.description), 1.131 + $.new("td", experiment.active), 1.132 + $.new("td", experiment.endDate), 1.133 + $.new("td", [ 1.134 + $.new("a", experiment.detailURL, null, {href : experiment.detailURL,}) 1.135 + ]), 1.136 + ]); 1.137 + })); 1.138 + }, 1.139 + 1.140 + modifiedPreferences: function modifiedPreferences(data) { 1.141 + $.append($("prefs-tbody"), sortedArrayFromObject(data).map( 1.142 + function ([name, value]) { 1.143 + return $.new("tr", [ 1.144 + $.new("td", name, "pref-name"), 1.145 + // Very long preference values can cause users problems when they 1.146 + // copy and paste them into some text editors. Long values generally 1.147 + // aren't useful anyway, so truncate them to a reasonable length. 1.148 + $.new("td", String(value).substr(0, 120), "pref-value"), 1.149 + ]); 1.150 + } 1.151 + )); 1.152 + }, 1.153 + 1.154 + graphics: function graphics(data) { 1.155 + // graphics-info-properties tbody 1.156 + if ("info" in data) { 1.157 + let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) { 1.158 + return $.new("tr", [ 1.159 + $.new("th", prop, "column"), 1.160 + $.new("td", String(val)), 1.161 + ]); 1.162 + }); 1.163 + $.append($("graphics-info-properties"), trs); 1.164 + delete data.info; 1.165 + } 1.166 + 1.167 + // graphics-failures-tbody tbody 1.168 + if ("failures" in data) { 1.169 + $.append($("graphics-failures-tbody"), data.failures.map(function (val) { 1.170 + return $.new("tr", [$.new("td", val)]); 1.171 + })); 1.172 + delete data.failures; 1.173 + } 1.174 + 1.175 + // graphics-tbody tbody 1.176 + 1.177 + function localizedMsg(msgArray) { 1.178 + let nameOrMsg = msgArray.shift(); 1.179 + if (msgArray.length) { 1.180 + // formatStringFromName logs an NS_ASSERTION failure otherwise that says 1.181 + // "use GetStringFromName". Lame. 1.182 + try { 1.183 + return strings.formatStringFromName(nameOrMsg, msgArray, 1.184 + msgArray.length); 1.185 + } 1.186 + catch (err) { 1.187 + // Throws if nameOrMsg is not a name in the bundle. This shouldn't 1.188 + // actually happen though, since msgArray.length > 1 => nameOrMsg is a 1.189 + // name in the bundle, not a message, and the remaining msgArray 1.190 + // elements are parameters. 1.191 + return nameOrMsg; 1.192 + } 1.193 + } 1.194 + try { 1.195 + return strings.GetStringFromName(nameOrMsg); 1.196 + } 1.197 + catch (err) { 1.198 + // Throws if nameOrMsg is not a name in the bundle. 1.199 + } 1.200 + return nameOrMsg; 1.201 + } 1.202 + 1.203 + let out = Object.create(data); 1.204 + let strings = stringBundle(); 1.205 + 1.206 + out.acceleratedWindows = 1.207 + data.numAcceleratedWindows + "/" + data.numTotalWindows; 1.208 + if (data.windowLayerManagerType) 1.209 + out.acceleratedWindows += " " + data.windowLayerManagerType; 1.210 + if (data.windowLayerManagerRemote) 1.211 + out.acceleratedWindows += " (OMTC)"; 1.212 + if (data.numAcceleratedWindowsMessage) 1.213 + out.acceleratedWindows += 1.214 + " " + localizedMsg(data.numAcceleratedWindowsMessage); 1.215 + delete data.numAcceleratedWindows; 1.216 + delete data.numTotalWindows; 1.217 + delete data.windowLayerManagerType; 1.218 + delete data.numAcceleratedWindowsMessage; 1.219 + 1.220 + if ("direct2DEnabledMessage" in data) { 1.221 + out.direct2DEnabled = localizedMsg(data.direct2DEnabledMessage); 1.222 + delete data.direct2DEnabledMessage; 1.223 + delete data.direct2DEnabled; 1.224 + } 1.225 + 1.226 + if ("directWriteEnabled" in data) { 1.227 + out.directWriteEnabled = data.directWriteEnabled; 1.228 + if ("directWriteVersion" in data) 1.229 + out.directWriteEnabled += " (" + data.directWriteVersion + ")"; 1.230 + delete data.directWriteEnabled; 1.231 + delete data.directWriteVersion; 1.232 + } 1.233 + 1.234 + if ("webglRendererMessage" in data) { 1.235 + out.webglRenderer = localizedMsg(data.webglRendererMessage); 1.236 + delete data.webglRendererMessage; 1.237 + delete data.webglRenderer; 1.238 + } 1.239 + 1.240 + let localizedOut = {}; 1.241 + for (let prop in out) { 1.242 + let val = out[prop]; 1.243 + if (typeof(val) == "string" && !val) 1.244 + // Ignore properties that are empty strings. 1.245 + continue; 1.246 + try { 1.247 + var localizedName = strings.GetStringFromName(prop); 1.248 + } 1.249 + catch (err) { 1.250 + // This shouldn't happen, but if there's a reported graphics property 1.251 + // that isn't in the string bundle, don't let it break the page. 1.252 + localizedName = prop; 1.253 + } 1.254 + localizedOut[localizedName] = val; 1.255 + } 1.256 + let trs = sortedArrayFromObject(localizedOut).map(function ([prop, val]) { 1.257 + return $.new("tr", [ 1.258 + $.new("th", prop, "column"), 1.259 + $.new("td", val), 1.260 + ]); 1.261 + }); 1.262 + $.append($("graphics-tbody"), trs); 1.263 + }, 1.264 + 1.265 + javaScript: function javaScript(data) { 1.266 + $("javascript-incremental-gc").textContent = data.incrementalGCEnabled; 1.267 + }, 1.268 + 1.269 + accessibility: function accessibility(data) { 1.270 + $("a11y-activated").textContent = data.isActive; 1.271 + $("a11y-force-disabled").textContent = data.forceDisabled || 0; 1.272 + }, 1.273 + 1.274 + libraryVersions: function libraryVersions(data) { 1.275 + let strings = stringBundle(); 1.276 + let trs = [ 1.277 + $.new("tr", [ 1.278 + $.new("th", ""), 1.279 + $.new("th", strings.GetStringFromName("minLibVersions")), 1.280 + $.new("th", strings.GetStringFromName("loadedLibVersions")), 1.281 + ]) 1.282 + ]; 1.283 + sortedArrayFromObject(data).forEach( 1.284 + function ([name, val]) { 1.285 + trs.push($.new("tr", [ 1.286 + $.new("td", name), 1.287 + $.new("td", val.minVersion), 1.288 + $.new("td", val.version), 1.289 + ])); 1.290 + } 1.291 + ); 1.292 + $.append($("libversions-tbody"), trs); 1.293 + }, 1.294 + 1.295 + userJS: function userJS(data) { 1.296 + if (!data.exists) 1.297 + return; 1.298 + let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile); 1.299 + userJSFile.append("user.js"); 1.300 + $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec; 1.301 + $("prefs-user-js-section").style.display = ""; 1.302 + // Clear the no-copy class 1.303 + $("prefs-user-js-section").className = ""; 1.304 + }, 1.305 +}; 1.306 + 1.307 +let $ = document.getElementById.bind(document); 1.308 + 1.309 +$.new = function $_new(tag, textContentOrChildren, className, attributes) { 1.310 + let elt = document.createElement(tag); 1.311 + if (className) 1.312 + elt.className = className; 1.313 + if (attributes) { 1.314 + for (let attrName in attributes) 1.315 + elt.setAttribute(attrName, attributes[attrName]); 1.316 + } 1.317 + if (Array.isArray(textContentOrChildren)) 1.318 + this.append(elt, textContentOrChildren); 1.319 + else 1.320 + elt.textContent = String(textContentOrChildren); 1.321 + return elt; 1.322 +}; 1.323 + 1.324 +$.append = function $_append(parent, children) { 1.325 + children.forEach(function (c) parent.appendChild(c)); 1.326 +}; 1.327 + 1.328 +function stringBundle() { 1.329 + return Services.strings.createBundle( 1.330 + "chrome://global/locale/aboutSupport.properties"); 1.331 +} 1.332 + 1.333 +function sortedArrayFromObject(obj) { 1.334 + let tuples = []; 1.335 + for (let prop in obj) 1.336 + tuples.push([prop, obj[prop]]); 1.337 + tuples.sort(function ([prop1, v1], [prop2, v2]) prop1.localeCompare(prop2)); 1.338 + return tuples; 1.339 +} 1.340 + 1.341 +function copyRawDataToClipboard(button) { 1.342 + if (button) 1.343 + button.disabled = true; 1.344 + try { 1.345 + Troubleshoot.snapshot(function (snapshot) { 1.346 + if (button) 1.347 + button.disabled = false; 1.348 + let str = Cc["@mozilla.org/supports-string;1"]. 1.349 + createInstance(Ci.nsISupportsString); 1.350 + str.data = JSON.stringify(snapshot, undefined, 2); 1.351 + let transferable = Cc["@mozilla.org/widget/transferable;1"]. 1.352 + createInstance(Ci.nsITransferable); 1.353 + transferable.init(getLoadContext()); 1.354 + transferable.addDataFlavor("text/unicode"); 1.355 + transferable.setTransferData("text/unicode", str, str.data.length * 2); 1.356 + Cc["@mozilla.org/widget/clipboard;1"]. 1.357 + getService(Ci.nsIClipboard). 1.358 + setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard); 1.359 +#ifdef ANDROID 1.360 + // Present a toast notification. 1.361 + let message = { 1.362 + type: "Toast:Show", 1.363 + message: stringBundle().GetStringFromName("rawDataCopied"), 1.364 + duration: "short" 1.365 + }; 1.366 + Services.androidBridge.handleGeckoMessage(message); 1.367 +#endif 1.368 + }); 1.369 + } 1.370 + catch (err) { 1.371 + if (button) 1.372 + button.disabled = false; 1.373 + throw err; 1.374 + } 1.375 +} 1.376 + 1.377 +function getLoadContext() { 1.378 + return window.QueryInterface(Ci.nsIInterfaceRequestor) 1.379 + .getInterface(Ci.nsIWebNavigation) 1.380 + .QueryInterface(Ci.nsILoadContext); 1.381 +} 1.382 + 1.383 +function copyContentsToClipboard() { 1.384 + // Get the HTML and text representations for the important part of the page. 1.385 + let contentsDiv = $("contents"); 1.386 + let dataHtml = contentsDiv.innerHTML; 1.387 + let dataText = createTextForElement(contentsDiv); 1.388 + 1.389 + // We can't use plain strings, we have to use nsSupportsString. 1.390 + let supportsStringClass = Cc["@mozilla.org/supports-string;1"]; 1.391 + let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString); 1.392 + let ssText = supportsStringClass.createInstance(Ci.nsISupportsString); 1.393 + 1.394 + let transferable = Cc["@mozilla.org/widget/transferable;1"] 1.395 + .createInstance(Ci.nsITransferable); 1.396 + transferable.init(getLoadContext()); 1.397 + 1.398 + // Add the HTML flavor. 1.399 + transferable.addDataFlavor("text/html"); 1.400 + ssHtml.data = dataHtml; 1.401 + transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2); 1.402 + 1.403 + // Add the plain text flavor. 1.404 + transferable.addDataFlavor("text/unicode"); 1.405 + ssText.data = dataText; 1.406 + transferable.setTransferData("text/unicode", ssText, dataText.length * 2); 1.407 + 1.408 + // Store the data into the clipboard. 1.409 + let clipboard = Cc["@mozilla.org/widget/clipboard;1"] 1.410 + .getService(Ci.nsIClipboard); 1.411 + clipboard.setData(transferable, null, clipboard.kGlobalClipboard); 1.412 + 1.413 +#ifdef ANDROID 1.414 + // Present a toast notification. 1.415 + let message = { 1.416 + type: "Toast:Show", 1.417 + message: stringBundle().GetStringFromName("textCopied"), 1.418 + duration: "short" 1.419 + }; 1.420 + Services.androidBridge.handleGeckoMessage(message); 1.421 +#endif 1.422 +} 1.423 + 1.424 +// Return the plain text representation of an element. Do a little bit 1.425 +// of pretty-printing to make it human-readable. 1.426 +function createTextForElement(elem) { 1.427 + let serializer = new Serializer(); 1.428 + let text = serializer.serialize(elem); 1.429 + 1.430 + // Actual CR/LF pairs are needed for some Windows text editors. 1.431 +#ifdef XP_WIN 1.432 + text = text.replace(/\n/g, "\r\n"); 1.433 +#endif 1.434 + 1.435 + return text; 1.436 +} 1.437 + 1.438 +function Serializer() { 1.439 +} 1.440 + 1.441 +Serializer.prototype = { 1.442 + 1.443 + serialize: function (rootElem) { 1.444 + this._lines = []; 1.445 + this._startNewLine(); 1.446 + this._serializeElement(rootElem); 1.447 + this._startNewLine(); 1.448 + return this._lines.join("\n").trim() + "\n"; 1.449 + }, 1.450 + 1.451 + // The current line is always the line that writing will start at next. When 1.452 + // an element is serialized, the current line is updated to be the line at 1.453 + // which the next element should be written. 1.454 + get _currentLine() { 1.455 + return this._lines.length ? this._lines[this._lines.length - 1] : null; 1.456 + }, 1.457 + 1.458 + set _currentLine(val) { 1.459 + return this._lines[this._lines.length - 1] = val; 1.460 + }, 1.461 + 1.462 + _serializeElement: function (elem) { 1.463 + if (this._ignoreElement(elem)) 1.464 + return; 1.465 + 1.466 + // table 1.467 + if (elem.localName == "table") { 1.468 + this._serializeTable(elem); 1.469 + return; 1.470 + } 1.471 + 1.472 + // all other elements 1.473 + 1.474 + let hasText = false; 1.475 + for (let child of elem.childNodes) { 1.476 + if (child.nodeType == Node.TEXT_NODE) { 1.477 + let text = this._nodeText(child); 1.478 + this._appendText(text); 1.479 + hasText = hasText || !!text.trim(); 1.480 + } 1.481 + else if (child.nodeType == Node.ELEMENT_NODE) 1.482 + this._serializeElement(child); 1.483 + } 1.484 + 1.485 + // For headings, draw a "line" underneath them so they stand out. 1.486 + if (/^h[0-9]+$/.test(elem.localName)) { 1.487 + let headerText = (this._currentLine || "").trim(); 1.488 + if (headerText) { 1.489 + this._startNewLine(); 1.490 + this._appendText("-".repeat(headerText.length)); 1.491 + } 1.492 + } 1.493 + 1.494 + // Add a blank line underneath block elements but only if they contain text. 1.495 + if (hasText) { 1.496 + let display = window.getComputedStyle(elem).getPropertyValue("display"); 1.497 + if (display == "block") { 1.498 + this._startNewLine(); 1.499 + this._startNewLine(); 1.500 + } 1.501 + } 1.502 + }, 1.503 + 1.504 + _startNewLine: function (lines) { 1.505 + let currLine = this._currentLine; 1.506 + if (currLine) { 1.507 + // The current line is not empty. Trim it. 1.508 + this._currentLine = currLine.trim(); 1.509 + if (!this._currentLine) 1.510 + // The current line became empty. Discard it. 1.511 + this._lines.pop(); 1.512 + } 1.513 + this._lines.push(""); 1.514 + }, 1.515 + 1.516 + _appendText: function (text, lines) { 1.517 + this._currentLine += text; 1.518 + }, 1.519 + 1.520 + _serializeTable: function (table) { 1.521 + // Collect the table's column headings if in fact there are any. First 1.522 + // check thead. If there's no thead, check the first tr. 1.523 + let colHeadings = {}; 1.524 + let tableHeadingElem = table.querySelector("thead"); 1.525 + if (!tableHeadingElem) 1.526 + tableHeadingElem = table.querySelector("tr"); 1.527 + if (tableHeadingElem) { 1.528 + let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td"); 1.529 + // If there's a contiguous run of th's in the children starting from the 1.530 + // rightmost child, then consider them to be column headings. 1.531 + for (let i = tableHeadingCols.length - 1; i >= 0; i--) { 1.532 + if (tableHeadingCols[i].localName != "th") 1.533 + break; 1.534 + colHeadings[i] = this._nodeText(tableHeadingCols[i]).trim(); 1.535 + } 1.536 + } 1.537 + let hasColHeadings = Object.keys(colHeadings).length > 0; 1.538 + if (!hasColHeadings) 1.539 + tableHeadingElem = null; 1.540 + 1.541 + let trs = table.querySelectorAll("table > tr, tbody > tr"); 1.542 + let startRow = 1.543 + tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0; 1.544 + 1.545 + if (startRow >= trs.length) 1.546 + // The table's empty. 1.547 + return; 1.548 + 1.549 + if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) { 1.550 + // Use column headings. Print each tr as a multi-line chunk like: 1.551 + // Heading 1: Column 1 value 1.552 + // Heading 2: Column 2 value 1.553 + for (let i = startRow; i < trs.length; i++) { 1.554 + if (this._ignoreElement(trs[i])) 1.555 + continue; 1.556 + let children = trs[i].querySelectorAll("td"); 1.557 + for (let j = 0; j < children.length; j++) { 1.558 + let text = ""; 1.559 + if (colHeadings[j]) 1.560 + text += colHeadings[j] + ": "; 1.561 + text += this._nodeText(children[j]).trim(); 1.562 + this._appendText(text); 1.563 + this._startNewLine(); 1.564 + } 1.565 + this._startNewLine(); 1.566 + } 1.567 + return; 1.568 + } 1.569 + 1.570 + // Don't use column headings. Assume the table has only two columns and 1.571 + // print each tr in a single line like: 1.572 + // Column 1 value: Column 2 value 1.573 + for (let i = startRow; i < trs.length; i++) { 1.574 + if (this._ignoreElement(trs[i])) 1.575 + continue; 1.576 + let children = trs[i].querySelectorAll("th,td"); 1.577 + let rowHeading = this._nodeText(children[0]).trim(); 1.578 + this._appendText(rowHeading + ": " + this._nodeText(children[1]).trim()); 1.579 + this._startNewLine(); 1.580 + } 1.581 + this._startNewLine(); 1.582 + }, 1.583 + 1.584 + _ignoreElement: function (elem) { 1.585 + return elem.classList.contains("no-copy"); 1.586 + }, 1.587 + 1.588 + _nodeText: function (node) { 1.589 + return node.textContent.replace(/\s+/g, " "); 1.590 + }, 1.591 +}; 1.592 + 1.593 +function openProfileDirectory() { 1.594 + // Get the profile directory. 1.595 + let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile); 1.596 + let profileDir = currProfD.path; 1.597 + 1.598 + // Show the profile directory. 1.599 + let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", 1.600 + "nsILocalFile", "initWithPath"); 1.601 + new nsLocalFile(profileDir).reveal(); 1.602 +} 1.603 + 1.604 +/** 1.605 + * Profile reset is only supported for the default profile if the appropriate migrator exists. 1.606 + */ 1.607 +function populateResetBox() { 1.608 + if (ResetProfile.resetSupported()) 1.609 + $("reset-box").style.visibility = "visible"; 1.610 +} 1.611 + 1.612 +/** 1.613 + * Set up event listeners for buttons. 1.614 + */ 1.615 +function setupEventListeners(){ 1.616 + $("show-update-history-button").addEventListener("click", function (event) { 1.617 + var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt); 1.618 + prompter.showUpdateHistory(window); 1.619 + }); 1.620 + $("reset-box-button").addEventListener("click", function (event){ 1.621 + ResetProfile.openConfirmationDialog(window); 1.622 + }); 1.623 + $("copy-raw-data-to-clipboard").addEventListener("click", function (event){ 1.624 + copyRawDataToClipboard(this); 1.625 + }); 1.626 + $("copy-to-clipboard").addEventListener("click", function (event){ 1.627 + copyContentsToClipboard(); 1.628 + }); 1.629 + $("profile-dir-button").addEventListener("click", function (event){ 1.630 + openProfileDirectory(); 1.631 + }); 1.632 +}