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