summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/about-support/content/export.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/about-support/content/export.js')
-rw-r--r--comm/mail/components/about-support/content/export.js288
1 files changed, 288 insertions, 0 deletions
diff --git a/comm/mail/components/about-support/content/export.js b/comm/mail/components/about-support/content/export.js
new file mode 100644
index 0000000000..46eb0c6497
--- /dev/null
+++ b/comm/mail/components/about-support/content/export.js
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals CLASS_DATA_PRIVATE, CLASS_DATA_PUBLIC, CLASS_DATA_UIONLY, createElement,
+createParentElement, getAccountsText, getLoadContext, MailServices, Services */
+
+"use strict";
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+/**
+ * Create warning text to add to any private data.
+ *
+ * @returns A HTML paragraph node containing the warning.
+ */
+function createWarning() {
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/aboutSupportMail.properties"
+ );
+ return createParentElement("p", [
+ createElement("strong", bundle.GetStringFromName("warningLabel")),
+ // Add some whitespace between the label and the text
+ document.createTextNode(" "),
+ document.createTextNode(bundle.GetStringFromName("warningText")),
+ ]);
+}
+
+function getClipboardTransferable() {
+ // Get the HTML and text representations for the important part of the page.
+ let hidePrivateData = !document.getElementById("check-show-private-data")
+ .checked;
+ let contentsDiv = createCleanedUpContents(hidePrivateData);
+ let dataHtml = contentsDiv.innerHTML;
+ let dataText = createTextForElement(contentsDiv, hidePrivateData);
+
+ // We can't use plain strings, we have to use nsSupportsString.
+ let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
+ let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
+ let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);
+
+ let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ transferable.init(getLoadContext());
+
+ // Add the HTML flavor.
+ transferable.addDataFlavor("text/html");
+ ssHtml.data = dataHtml;
+ transferable.setTransferData("text/html", ssHtml);
+
+ // Add the plain text flavor.
+ transferable.addDataFlavor("text/plain");
+ ssText.data = dataText;
+ transferable.setTransferData("text/plain", ssText);
+
+ return transferable;
+}
+
+// This function intentionally has the same name as the one in aboutSupport.js
+// so that the one here is called.
+function copyContentsToClipboard() {
+ let transferable = getClipboardTransferable();
+ // Store the data into the clipboard.
+ Services.clipboard.setData(
+ transferable,
+ null,
+ Services.clipboard.kGlobalClipboard
+ );
+}
+
+function sendViaEmail() {
+ // Get the HTML representation for the important part of the page.
+ let hidePrivateData = !document.getElementById("check-show-private-data")
+ .checked;
+ let contentsDiv = createCleanedUpContents(hidePrivateData);
+ let dataHtml = contentsDiv.innerHTML;
+ // The editor considers whitespace to be significant, so replace all
+ // whitespace with a single space.
+ dataHtml = dataHtml.replace(/\s+/g, " ");
+
+ // Set up parameters and fields to use for the compose window.
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.type = Ci.nsIMsgCompType.New;
+ params.format = Ci.nsIMsgCompFormat.HTML;
+
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.forcePlainText = false;
+ fields.body = dataHtml;
+ // In general we can have non-ASCII characters, and compose's charset
+ // detection doesn't seem to work when the HTML part is pure ASCII but the
+ // text isn't. So take the easy way out and force UTF-8.
+ fields.bodyIsAsciiOnly = false;
+ params.composeFields = fields;
+
+ // Our params are set up. Now open a compose window.
+ MailServices.compose.OpenComposeWindowWithParams(null, params);
+}
+
+function createCleanedUpContents(aHidePrivateData) {
+ // Get the important part of the page.
+ let contentsDiv = document.getElementById("contents");
+ // Deep-clone the entire div.
+ let clonedDiv = contentsDiv.cloneNode(true);
+ // Go in and replace text with the text we actually want to copy.
+ // (this mutates the cloned node)
+ cleanUpText(clonedDiv, aHidePrivateData);
+ // Insert a warning if we need to
+ if (!aHidePrivateData) {
+ clonedDiv.insertBefore(createWarning(), clonedDiv.firstChild);
+ }
+ return clonedDiv;
+}
+
+function cleanUpText(aElem, aHidePrivateData) {
+ let node = aElem.firstChild;
+ let copyData = aElem.dataset.copyData;
+ delete aElem.dataset.copyData;
+ while (node) {
+ let classList = "classList" in node && node.classList;
+ // Delete uionly and no-copy nodes.
+ if (
+ classList &&
+ (classList.contains(CLASS_DATA_UIONLY) || classList.contains("no-copy"))
+ ) {
+ // Advance to the next node before removing the current node, since
+ // node.nextElementSibling is null after remove()
+ let nextNode = node.nextElementSibling;
+ node.remove();
+ node = nextNode;
+ continue;
+ } else if (
+ aHidePrivateData &&
+ classList &&
+ classList.contains(CLASS_DATA_PRIVATE)
+ ) {
+ // Replace private data with a blank string.
+ node.textContent = "";
+ } else if (
+ !aHidePrivateData &&
+ classList &&
+ classList.contains(CLASS_DATA_PUBLIC)
+ ) {
+ // Replace public data with a blank string.
+ node.textContent = "";
+ } else if (copyData != null) {
+ // Replace localized text with non-localized text.
+ node.textContent = copyData;
+ copyData = null;
+ }
+
+ if (node.nodeType == Node.ELEMENT_NODE) {
+ cleanUpText(node, aHidePrivateData);
+ }
+
+ // Advance!
+ node = node.nextSibling;
+ }
+}
+
+// Return the plain text representation of an element. Do a little bit
+// of pretty-printing to make it human-readable.
+function createTextForElement(elem, aHidePrivateData) {
+ // Generate the initial text.
+ let textFragmentAccumulator = [];
+ generateTextForElement(elem, aHidePrivateData, "", textFragmentAccumulator);
+ let text = textFragmentAccumulator.join("");
+
+ // Trim extraneous whitespace before newlines, then squash extraneous
+ // blank lines.
+ text = text.replace(/[ \t]+\n/g, "\n");
+ text = text.replace(/\n{3,}/g, "\n\n");
+
+ // Actual CR/LF pairs are needed for some Windows text editors.
+ if ("@mozilla.org/windows-registry-key;1" in Cc) {
+ text = text.replace(/\n/g, "\r\n");
+ }
+
+ return text;
+}
+
+/**
+ * Elements to replace entirely with custom text. Keys are element ids, values
+ * are functions that return the text. The functions themselves are defined in
+ * the files for their respective sections.
+ */
+var gElementsToReplace = {
+ "accounts-table": getAccountsText,
+};
+
+function generateTextForElement(
+ elem,
+ aHidePrivateData,
+ indent,
+ textFragmentAccumulator
+) {
+ // Add a little extra spacing around most elements.
+ if (!["td", "th", "span", "a"].includes(elem.tagName)) {
+ textFragmentAccumulator.push("\n");
+ }
+
+ // If this element is one of our elements to replace with text, do it.
+ if (elem.id in gElementsToReplace) {
+ let replaceFn = gElementsToReplace[elem.id];
+ textFragmentAccumulator.push(replaceFn(aHidePrivateData, indent + " "));
+ return;
+ }
+
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ if (elem.id == "crashes-table") {
+ textFragmentAccumulator.push(getCrashesText(indent));
+ return;
+ }
+ }
+
+ let childCount = elem.childElementCount;
+
+ // We're not going to spread a two-column <tr> across multiple lines, so
+ // handle that separately.
+ if (elem.tagName == "tr" && childCount == 2) {
+ textFragmentAccumulator.push(indent);
+ textFragmentAccumulator.push(
+ elem.children[0].textContent.trim() +
+ ": " +
+ elem.children[1].textContent.trim()
+ );
+ return;
+ }
+
+ // Generate the text representation for each child node.
+ let node = elem.firstChild;
+ while (node) {
+ if (node.nodeType == Node.TEXT_NODE) {
+ // Text belonging to this element uses its indentation level.
+ generateTextForTextNode(node, indent, textFragmentAccumulator);
+ } else if (node.nodeType == Node.ELEMENT_NODE) {
+ // Recurse on the child element with an extra level of indentation (but
+ // only if there's more than one child).
+ generateTextForElement(
+ node,
+ aHidePrivateData,
+ indent + (childCount > 1 ? " " : ""),
+ textFragmentAccumulator
+ );
+ }
+ // Advance!
+ node = node.nextSibling;
+ }
+}
+
+function generateTextForTextNode(node, indent, textFragmentAccumulator) {
+ // If the text node is the first of a run of text nodes, then start
+ // a new line and add the initial indentation.
+ let prevNode = node.previousSibling;
+ if (!prevNode || prevNode.nodeType == Node.TEXT_NODE) {
+ textFragmentAccumulator.push("\n" + indent);
+ }
+
+ // Trim the text node's text content and add proper indentation after
+ // any internal line breaks.
+ let text = node.textContent.trim().replace(/\n/g, "\n" + indent);
+ textFragmentAccumulator.push(text);
+}
+
+/**
+ * Returns a plaintext representation of crashes data.
+ */
+
+function getCrashesText(aIndent) {
+ let crashesData = "";
+ let recentCrashesSubmitted = document.querySelectorAll("#crashes-tbody > tr");
+ for (let i = 0; i < recentCrashesSubmitted.length; i++) {
+ let tds = recentCrashesSubmitted.item(i).querySelectorAll("td");
+ crashesData +=
+ aIndent.repeat(2) +
+ tds.item(0).firstElementChild.href +
+ " (" +
+ tds.item(1).textContent +
+ ")\n";
+ }
+ return crashesData;
+}