michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Troubleshoot.jsm"); michael@0: Cu.import("resource://gre/modules/ResetProfile.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: window.addEventListener("load", function onload(event) { michael@0: try { michael@0: window.removeEventListener("load", onload, false); michael@0: Troubleshoot.snapshot(function (snapshot) { michael@0: for (let prop in snapshotFormatters) michael@0: snapshotFormatters[prop](snapshot[prop]); michael@0: }); michael@0: populateResetBox(); michael@0: setupEventListeners(); michael@0: } catch (e) { michael@0: Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack); michael@0: } michael@0: }, false); michael@0: michael@0: // Each property in this object corresponds to a property in Troubleshoot.jsm's michael@0: // snapshot data. Each function is passed its property's corresponding data, michael@0: // and it's the function's job to update the page with it. michael@0: let snapshotFormatters = { michael@0: michael@0: application: function application(data) { michael@0: $("application-box").textContent = data.name; michael@0: $("useragent-box").textContent = data.userAgent; michael@0: $("supportLink").href = data.supportURL; michael@0: let version = data.version; michael@0: if (data.vendor) michael@0: version += " (" + data.vendor + ")"; michael@0: $("version-box").textContent = version; michael@0: }, michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: crashes: function crashes(data) { michael@0: let strings = stringBundle(); michael@0: let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000); michael@0: $("crashes-title").textContent = michael@0: PluralForm.get(daysRange, strings.GetStringFromName("crashesTitle")) michael@0: .replace("#1", daysRange); michael@0: let reportURL; michael@0: try { michael@0: reportURL = Services.prefs.getCharPref("breakpad.reportURL"); michael@0: // Ignore any non http/https urls michael@0: if (!/^https?:/i.test(reportURL)) michael@0: reportURL = null; michael@0: } michael@0: catch (e) { } michael@0: if (!reportURL) { michael@0: $("crashes-noConfig").style.display = "block"; michael@0: $("crashes-noConfig").classList.remove("no-copy"); michael@0: return; michael@0: } michael@0: else { michael@0: $("crashes-allReports").style.display = "block"; michael@0: $("crashes-allReports").classList.remove("no-copy"); michael@0: } michael@0: michael@0: if (data.pending > 0) { michael@0: $("crashes-allReportsWithPending").textContent = michael@0: PluralForm.get(data.pending, strings.GetStringFromName("pendingReports")) michael@0: .replace("#1", data.pending); michael@0: } michael@0: michael@0: let dateNow = new Date(); michael@0: $.append($("crashes-tbody"), data.submitted.map(function (crash) { michael@0: let date = new Date(crash.date); michael@0: let timePassed = dateNow - date; michael@0: let formattedDate; michael@0: if (timePassed >= 24 * 60 * 60 * 1000) michael@0: { michael@0: let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000)); michael@0: let daysPassedString = strings.GetStringFromName("crashesTimeDays"); michael@0: formattedDate = PluralForm.get(daysPassed, daysPassedString) michael@0: .replace("#1", daysPassed); michael@0: } michael@0: else if (timePassed >= 60 * 60 * 1000) michael@0: { michael@0: let hoursPassed = Math.round(timePassed / (60 * 60 * 1000)); michael@0: let hoursPassedString = strings.GetStringFromName("crashesTimeHours"); michael@0: formattedDate = PluralForm.get(hoursPassed, hoursPassedString) michael@0: .replace("#1", hoursPassed); michael@0: } michael@0: else michael@0: { michael@0: let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1); michael@0: let minutesPassedString = strings.GetStringFromName("crashesTimeMinutes"); michael@0: formattedDate = PluralForm.get(minutesPassed, minutesPassedString) michael@0: .replace("#1", minutesPassed); michael@0: } michael@0: return $.new("tr", [ michael@0: $.new("td", [ michael@0: $.new("a", crash.id, null, {href : reportURL + crash.id}) michael@0: ]), michael@0: $.new("td", formattedDate) michael@0: ]); michael@0: })); michael@0: }, michael@0: #endif michael@0: michael@0: extensions: function extensions(data) { michael@0: $.append($("extensions-tbody"), data.map(function (extension) { michael@0: return $.new("tr", [ michael@0: $.new("td", extension.name), michael@0: $.new("td", extension.version), michael@0: $.new("td", extension.isActive), michael@0: $.new("td", extension.id), michael@0: ]); michael@0: })); michael@0: }, michael@0: michael@0: experiments: function experiments(data) { michael@0: $.append($("experiments-tbody"), data.map(function (experiment) { michael@0: return $.new("tr", [ michael@0: $.new("td", experiment.name), michael@0: $.new("td", experiment.id), michael@0: $.new("td", experiment.description), michael@0: $.new("td", experiment.active), michael@0: $.new("td", experiment.endDate), michael@0: $.new("td", [ michael@0: $.new("a", experiment.detailURL, null, {href : experiment.detailURL,}) michael@0: ]), michael@0: ]); michael@0: })); michael@0: }, michael@0: michael@0: modifiedPreferences: function modifiedPreferences(data) { michael@0: $.append($("prefs-tbody"), sortedArrayFromObject(data).map( michael@0: function ([name, value]) { michael@0: return $.new("tr", [ michael@0: $.new("td", name, "pref-name"), michael@0: // Very long preference values can cause users problems when they michael@0: // copy and paste them into some text editors. Long values generally michael@0: // aren't useful anyway, so truncate them to a reasonable length. michael@0: $.new("td", String(value).substr(0, 120), "pref-value"), michael@0: ]); michael@0: } michael@0: )); michael@0: }, michael@0: michael@0: graphics: function graphics(data) { michael@0: // graphics-info-properties tbody michael@0: if ("info" in data) { michael@0: let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) { michael@0: return $.new("tr", [ michael@0: $.new("th", prop, "column"), michael@0: $.new("td", String(val)), michael@0: ]); michael@0: }); michael@0: $.append($("graphics-info-properties"), trs); michael@0: delete data.info; michael@0: } michael@0: michael@0: // graphics-failures-tbody tbody michael@0: if ("failures" in data) { michael@0: $.append($("graphics-failures-tbody"), data.failures.map(function (val) { michael@0: return $.new("tr", [$.new("td", val)]); michael@0: })); michael@0: delete data.failures; michael@0: } michael@0: michael@0: // graphics-tbody tbody michael@0: michael@0: function localizedMsg(msgArray) { michael@0: let nameOrMsg = msgArray.shift(); michael@0: if (msgArray.length) { michael@0: // formatStringFromName logs an NS_ASSERTION failure otherwise that says michael@0: // "use GetStringFromName". Lame. michael@0: try { michael@0: return strings.formatStringFromName(nameOrMsg, msgArray, michael@0: msgArray.length); michael@0: } michael@0: catch (err) { michael@0: // Throws if nameOrMsg is not a name in the bundle. This shouldn't michael@0: // actually happen though, since msgArray.length > 1 => nameOrMsg is a michael@0: // name in the bundle, not a message, and the remaining msgArray michael@0: // elements are parameters. michael@0: return nameOrMsg; michael@0: } michael@0: } michael@0: try { michael@0: return strings.GetStringFromName(nameOrMsg); michael@0: } michael@0: catch (err) { michael@0: // Throws if nameOrMsg is not a name in the bundle. michael@0: } michael@0: return nameOrMsg; michael@0: } michael@0: michael@0: let out = Object.create(data); michael@0: let strings = stringBundle(); michael@0: michael@0: out.acceleratedWindows = michael@0: data.numAcceleratedWindows + "/" + data.numTotalWindows; michael@0: if (data.windowLayerManagerType) michael@0: out.acceleratedWindows += " " + data.windowLayerManagerType; michael@0: if (data.windowLayerManagerRemote) michael@0: out.acceleratedWindows += " (OMTC)"; michael@0: if (data.numAcceleratedWindowsMessage) michael@0: out.acceleratedWindows += michael@0: " " + localizedMsg(data.numAcceleratedWindowsMessage); michael@0: delete data.numAcceleratedWindows; michael@0: delete data.numTotalWindows; michael@0: delete data.windowLayerManagerType; michael@0: delete data.numAcceleratedWindowsMessage; michael@0: michael@0: if ("direct2DEnabledMessage" in data) { michael@0: out.direct2DEnabled = localizedMsg(data.direct2DEnabledMessage); michael@0: delete data.direct2DEnabledMessage; michael@0: delete data.direct2DEnabled; michael@0: } michael@0: michael@0: if ("directWriteEnabled" in data) { michael@0: out.directWriteEnabled = data.directWriteEnabled; michael@0: if ("directWriteVersion" in data) michael@0: out.directWriteEnabled += " (" + data.directWriteVersion + ")"; michael@0: delete data.directWriteEnabled; michael@0: delete data.directWriteVersion; michael@0: } michael@0: michael@0: if ("webglRendererMessage" in data) { michael@0: out.webglRenderer = localizedMsg(data.webglRendererMessage); michael@0: delete data.webglRendererMessage; michael@0: delete data.webglRenderer; michael@0: } michael@0: michael@0: let localizedOut = {}; michael@0: for (let prop in out) { michael@0: let val = out[prop]; michael@0: if (typeof(val) == "string" && !val) michael@0: // Ignore properties that are empty strings. michael@0: continue; michael@0: try { michael@0: var localizedName = strings.GetStringFromName(prop); michael@0: } michael@0: catch (err) { michael@0: // This shouldn't happen, but if there's a reported graphics property michael@0: // that isn't in the string bundle, don't let it break the page. michael@0: localizedName = prop; michael@0: } michael@0: localizedOut[localizedName] = val; michael@0: } michael@0: let trs = sortedArrayFromObject(localizedOut).map(function ([prop, val]) { michael@0: return $.new("tr", [ michael@0: $.new("th", prop, "column"), michael@0: $.new("td", val), michael@0: ]); michael@0: }); michael@0: $.append($("graphics-tbody"), trs); michael@0: }, michael@0: michael@0: javaScript: function javaScript(data) { michael@0: $("javascript-incremental-gc").textContent = data.incrementalGCEnabled; michael@0: }, michael@0: michael@0: accessibility: function accessibility(data) { michael@0: $("a11y-activated").textContent = data.isActive; michael@0: $("a11y-force-disabled").textContent = data.forceDisabled || 0; michael@0: }, michael@0: michael@0: libraryVersions: function libraryVersions(data) { michael@0: let strings = stringBundle(); michael@0: let trs = [ michael@0: $.new("tr", [ michael@0: $.new("th", ""), michael@0: $.new("th", strings.GetStringFromName("minLibVersions")), michael@0: $.new("th", strings.GetStringFromName("loadedLibVersions")), michael@0: ]) michael@0: ]; michael@0: sortedArrayFromObject(data).forEach( michael@0: function ([name, val]) { michael@0: trs.push($.new("tr", [ michael@0: $.new("td", name), michael@0: $.new("td", val.minVersion), michael@0: $.new("td", val.version), michael@0: ])); michael@0: } michael@0: ); michael@0: $.append($("libversions-tbody"), trs); michael@0: }, michael@0: michael@0: userJS: function userJS(data) { michael@0: if (!data.exists) michael@0: return; michael@0: let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile); michael@0: userJSFile.append("user.js"); michael@0: $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec; michael@0: $("prefs-user-js-section").style.display = ""; michael@0: // Clear the no-copy class michael@0: $("prefs-user-js-section").className = ""; michael@0: }, michael@0: }; michael@0: michael@0: let $ = document.getElementById.bind(document); michael@0: michael@0: $.new = function $_new(tag, textContentOrChildren, className, attributes) { michael@0: let elt = document.createElement(tag); michael@0: if (className) michael@0: elt.className = className; michael@0: if (attributes) { michael@0: for (let attrName in attributes) michael@0: elt.setAttribute(attrName, attributes[attrName]); michael@0: } michael@0: if (Array.isArray(textContentOrChildren)) michael@0: this.append(elt, textContentOrChildren); michael@0: else michael@0: elt.textContent = String(textContentOrChildren); michael@0: return elt; michael@0: }; michael@0: michael@0: $.append = function $_append(parent, children) { michael@0: children.forEach(function (c) parent.appendChild(c)); michael@0: }; michael@0: michael@0: function stringBundle() { michael@0: return Services.strings.createBundle( michael@0: "chrome://global/locale/aboutSupport.properties"); michael@0: } michael@0: michael@0: function sortedArrayFromObject(obj) { michael@0: let tuples = []; michael@0: for (let prop in obj) michael@0: tuples.push([prop, obj[prop]]); michael@0: tuples.sort(function ([prop1, v1], [prop2, v2]) prop1.localeCompare(prop2)); michael@0: return tuples; michael@0: } michael@0: michael@0: function copyRawDataToClipboard(button) { michael@0: if (button) michael@0: button.disabled = true; michael@0: try { michael@0: Troubleshoot.snapshot(function (snapshot) { michael@0: if (button) michael@0: button.disabled = false; michael@0: let str = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: str.data = JSON.stringify(snapshot, undefined, 2); michael@0: let transferable = Cc["@mozilla.org/widget/transferable;1"]. michael@0: createInstance(Ci.nsITransferable); michael@0: transferable.init(getLoadContext()); michael@0: transferable.addDataFlavor("text/unicode"); michael@0: transferable.setTransferData("text/unicode", str, str.data.length * 2); michael@0: Cc["@mozilla.org/widget/clipboard;1"]. michael@0: getService(Ci.nsIClipboard). michael@0: setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard); michael@0: #ifdef ANDROID michael@0: // Present a toast notification. michael@0: let message = { michael@0: type: "Toast:Show", michael@0: message: stringBundle().GetStringFromName("rawDataCopied"), michael@0: duration: "short" michael@0: }; michael@0: Services.androidBridge.handleGeckoMessage(message); michael@0: #endif michael@0: }); michael@0: } michael@0: catch (err) { michael@0: if (button) michael@0: button.disabled = false; michael@0: throw err; michael@0: } michael@0: } michael@0: michael@0: function getLoadContext() { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsILoadContext); michael@0: } michael@0: michael@0: function copyContentsToClipboard() { michael@0: // Get the HTML and text representations for the important part of the page. michael@0: let contentsDiv = $("contents"); michael@0: let dataHtml = contentsDiv.innerHTML; michael@0: let dataText = createTextForElement(contentsDiv); michael@0: michael@0: // We can't use plain strings, we have to use nsSupportsString. michael@0: let supportsStringClass = Cc["@mozilla.org/supports-string;1"]; michael@0: let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString); michael@0: let ssText = supportsStringClass.createInstance(Ci.nsISupportsString); michael@0: michael@0: let transferable = Cc["@mozilla.org/widget/transferable;1"] michael@0: .createInstance(Ci.nsITransferable); michael@0: transferable.init(getLoadContext()); michael@0: michael@0: // Add the HTML flavor. michael@0: transferable.addDataFlavor("text/html"); michael@0: ssHtml.data = dataHtml; michael@0: transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2); michael@0: michael@0: // Add the plain text flavor. michael@0: transferable.addDataFlavor("text/unicode"); michael@0: ssText.data = dataText; michael@0: transferable.setTransferData("text/unicode", ssText, dataText.length * 2); michael@0: michael@0: // Store the data into the clipboard. michael@0: let clipboard = Cc["@mozilla.org/widget/clipboard;1"] michael@0: .getService(Ci.nsIClipboard); michael@0: clipboard.setData(transferable, null, clipboard.kGlobalClipboard); michael@0: michael@0: #ifdef ANDROID michael@0: // Present a toast notification. michael@0: let message = { michael@0: type: "Toast:Show", michael@0: message: stringBundle().GetStringFromName("textCopied"), michael@0: duration: "short" michael@0: }; michael@0: Services.androidBridge.handleGeckoMessage(message); michael@0: #endif michael@0: } michael@0: michael@0: // Return the plain text representation of an element. Do a little bit michael@0: // of pretty-printing to make it human-readable. michael@0: function createTextForElement(elem) { michael@0: let serializer = new Serializer(); michael@0: let text = serializer.serialize(elem); michael@0: michael@0: // Actual CR/LF pairs are needed for some Windows text editors. michael@0: #ifdef XP_WIN michael@0: text = text.replace(/\n/g, "\r\n"); michael@0: #endif michael@0: michael@0: return text; michael@0: } michael@0: michael@0: function Serializer() { michael@0: } michael@0: michael@0: Serializer.prototype = { michael@0: michael@0: serialize: function (rootElem) { michael@0: this._lines = []; michael@0: this._startNewLine(); michael@0: this._serializeElement(rootElem); michael@0: this._startNewLine(); michael@0: return this._lines.join("\n").trim() + "\n"; michael@0: }, michael@0: michael@0: // The current line is always the line that writing will start at next. When michael@0: // an element is serialized, the current line is updated to be the line at michael@0: // which the next element should be written. michael@0: get _currentLine() { michael@0: return this._lines.length ? this._lines[this._lines.length - 1] : null; michael@0: }, michael@0: michael@0: set _currentLine(val) { michael@0: return this._lines[this._lines.length - 1] = val; michael@0: }, michael@0: michael@0: _serializeElement: function (elem) { michael@0: if (this._ignoreElement(elem)) michael@0: return; michael@0: michael@0: // table michael@0: if (elem.localName == "table") { michael@0: this._serializeTable(elem); michael@0: return; michael@0: } michael@0: michael@0: // all other elements michael@0: michael@0: let hasText = false; michael@0: for (let child of elem.childNodes) { michael@0: if (child.nodeType == Node.TEXT_NODE) { michael@0: let text = this._nodeText(child); michael@0: this._appendText(text); michael@0: hasText = hasText || !!text.trim(); michael@0: } michael@0: else if (child.nodeType == Node.ELEMENT_NODE) michael@0: this._serializeElement(child); michael@0: } michael@0: michael@0: // For headings, draw a "line" underneath them so they stand out. michael@0: if (/^h[0-9]+$/.test(elem.localName)) { michael@0: let headerText = (this._currentLine || "").trim(); michael@0: if (headerText) { michael@0: this._startNewLine(); michael@0: this._appendText("-".repeat(headerText.length)); michael@0: } michael@0: } michael@0: michael@0: // Add a blank line underneath block elements but only if they contain text. michael@0: if (hasText) { michael@0: let display = window.getComputedStyle(elem).getPropertyValue("display"); michael@0: if (display == "block") { michael@0: this._startNewLine(); michael@0: this._startNewLine(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _startNewLine: function (lines) { michael@0: let currLine = this._currentLine; michael@0: if (currLine) { michael@0: // The current line is not empty. Trim it. michael@0: this._currentLine = currLine.trim(); michael@0: if (!this._currentLine) michael@0: // The current line became empty. Discard it. michael@0: this._lines.pop(); michael@0: } michael@0: this._lines.push(""); michael@0: }, michael@0: michael@0: _appendText: function (text, lines) { michael@0: this._currentLine += text; michael@0: }, michael@0: michael@0: _serializeTable: function (table) { michael@0: // Collect the table's column headings if in fact there are any. First michael@0: // check thead. If there's no thead, check the first tr. michael@0: let colHeadings = {}; michael@0: let tableHeadingElem = table.querySelector("thead"); michael@0: if (!tableHeadingElem) michael@0: tableHeadingElem = table.querySelector("tr"); michael@0: if (tableHeadingElem) { michael@0: let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td"); michael@0: // If there's a contiguous run of th's in the children starting from the michael@0: // rightmost child, then consider them to be column headings. michael@0: for (let i = tableHeadingCols.length - 1; i >= 0; i--) { michael@0: if (tableHeadingCols[i].localName != "th") michael@0: break; michael@0: colHeadings[i] = this._nodeText(tableHeadingCols[i]).trim(); michael@0: } michael@0: } michael@0: let hasColHeadings = Object.keys(colHeadings).length > 0; michael@0: if (!hasColHeadings) michael@0: tableHeadingElem = null; michael@0: michael@0: let trs = table.querySelectorAll("table > tr, tbody > tr"); michael@0: let startRow = michael@0: tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0; michael@0: michael@0: if (startRow >= trs.length) michael@0: // The table's empty. michael@0: return; michael@0: michael@0: if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) { michael@0: // Use column headings. Print each tr as a multi-line chunk like: michael@0: // Heading 1: Column 1 value michael@0: // Heading 2: Column 2 value michael@0: for (let i = startRow; i < trs.length; i++) { michael@0: if (this._ignoreElement(trs[i])) michael@0: continue; michael@0: let children = trs[i].querySelectorAll("td"); michael@0: for (let j = 0; j < children.length; j++) { michael@0: let text = ""; michael@0: if (colHeadings[j]) michael@0: text += colHeadings[j] + ": "; michael@0: text += this._nodeText(children[j]).trim(); michael@0: this._appendText(text); michael@0: this._startNewLine(); michael@0: } michael@0: this._startNewLine(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Don't use column headings. Assume the table has only two columns and michael@0: // print each tr in a single line like: michael@0: // Column 1 value: Column 2 value michael@0: for (let i = startRow; i < trs.length; i++) { michael@0: if (this._ignoreElement(trs[i])) michael@0: continue; michael@0: let children = trs[i].querySelectorAll("th,td"); michael@0: let rowHeading = this._nodeText(children[0]).trim(); michael@0: this._appendText(rowHeading + ": " + this._nodeText(children[1]).trim()); michael@0: this._startNewLine(); michael@0: } michael@0: this._startNewLine(); michael@0: }, michael@0: michael@0: _ignoreElement: function (elem) { michael@0: return elem.classList.contains("no-copy"); michael@0: }, michael@0: michael@0: _nodeText: function (node) { michael@0: return node.textContent.replace(/\s+/g, " "); michael@0: }, michael@0: }; michael@0: michael@0: function openProfileDirectory() { michael@0: // Get the profile directory. michael@0: let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile); michael@0: let profileDir = currProfD.path; michael@0: michael@0: // Show the profile directory. michael@0: let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", michael@0: "nsILocalFile", "initWithPath"); michael@0: new nsLocalFile(profileDir).reveal(); michael@0: } michael@0: michael@0: /** michael@0: * Profile reset is only supported for the default profile if the appropriate migrator exists. michael@0: */ michael@0: function populateResetBox() { michael@0: if (ResetProfile.resetSupported()) michael@0: $("reset-box").style.visibility = "visible"; michael@0: } michael@0: michael@0: /** michael@0: * Set up event listeners for buttons. michael@0: */ michael@0: function setupEventListeners(){ michael@0: $("show-update-history-button").addEventListener("click", function (event) { michael@0: var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt); michael@0: prompter.showUpdateHistory(window); michael@0: }); michael@0: $("reset-box-button").addEventListener("click", function (event){ michael@0: ResetProfile.openConfirmationDialog(window); michael@0: }); michael@0: $("copy-raw-data-to-clipboard").addEventListener("click", function (event){ michael@0: copyRawDataToClipboard(this); michael@0: }); michael@0: $("copy-to-clipboard").addEventListener("click", function (event){ michael@0: copyContentsToClipboard(); michael@0: }); michael@0: $("profile-dir-button").addEventListener("click", function (event){ michael@0: openProfileDirectory(); michael@0: }); michael@0: }