+
+
&generic.title;
+ &captivePortal.title;
+ &dnsNotFound.title;
+ &fileNotFound.title;
+ &fileAccessDenied.title;
+
+ &unknownProtocolFound.title;
+ &connectionFailure.title;
+ &netTimeout.title;
+ &redirectLoop.title;
+ &unknownSocketType.title;
+ &netReset.title;
+ ¬Cached.title;
+ &netOffline.title;
+ &netInterrupt.title;
+ &deniedPortAccess.title;
+ &proxyResolveFailure.title;
+ &proxyConnectFailure.title;
+ &contentEncodingError.title;
+ &unsafeContentType.title;
+ &nssFailure2.title;
+ &certerror.longpagetitle1;
+ &cspBlocked.title;
+ &remoteXUL.title;
+ &corruptedContentErrorv2.title;
+ &sslv3Used.title;
+ &weakCryptoUsed.title;
+ &inadequateSecurityError.title;
+
+
+
&generic.longDesc;
+
&captivePortal.longDesc;
+
&dnsNotFound.longDesc;
+
&fileNotFound.longDesc;
+
&fileAccessDenied.longDesc;
+
&malformedURI.longDesc;
+
&unknownProtocolFound.longDesc;
+
&connectionFailure.longDesc;
+
&netTimeout.longDesc;
+
&redirectLoop.longDesc;
+
&unknownSocketType.longDesc;
+
&netReset.longDesc;
+
¬Cached.longDesc;
+
&netOffline.longDesc2;
+
&netInterrupt.longDesc;
+
&deniedPortAccess.longDesc;
+
&proxyResolveFailure.longDesc;
+
&proxyConnectFailure.longDesc;
+
&contentEncodingError.longDesc;
+
&unsafeContentType.longDesc;
+
&nssFailure2.longDesc2;
+
&certerror.introPara;
+
&cspBlocked.longDesc;
+
&remoteXUL.longDesc;
+
&corruptedContentErrorv2.longDesc;
+
&sslv3Used.longDesc2;
+
&weakCryptoUsed.longDesc2;
+
&inadequateSecurityError.longDesc;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
&certerror.whatShouldIDo.badStsCertExplanation;
+
+
+ &certerror.wrongSystemTime;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
&weakCryptoAdvanced.longDesc;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/basilisk/base/content/aboutTabCrashed.css b/basilisk/base/content/aboutTabCrashed.css
new file mode 100644
index 000000000..de0eabe8b
--- /dev/null
+++ b/basilisk/base/content/aboutTabCrashed.css
@@ -0,0 +1,11 @@
+/* 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/. */
+
+html:not(.crashDumpSubmitted) #reportSent,
+html:not(.crashDumpAvailable) #reportBox,
+.container[multiple="true"] > .offers > #offerHelpMessageSingle,
+.container[multiple="false"] > .offers > #offerHelpMessageMultiple,
+.container[multiple="false"] > .button-container > #restoreAll {
+ display: none;
+}
\ No newline at end of file
diff --git a/basilisk/base/content/aboutTabCrashed.js b/basilisk/base/content/aboutTabCrashed.js
new file mode 100644
index 000000000..337add1d2
--- /dev/null
+++ b/basilisk/base/content/aboutTabCrashed.js
@@ -0,0 +1,309 @@
+/* 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/. */
+
+var AboutTabCrashed = {
+ /**
+ * This can be set to true once this page receives a message from the
+ * parent saying whether or not a crash report is available.
+ */
+ hasReport: false,
+
+ /**
+ * The messages that we might receive from the parent.
+ */
+ MESSAGES: [
+ "SetCrashReportAvailable",
+ "CrashReportSent",
+ "UpdateCount",
+ ],
+
+ /**
+ * Items for which we will listen for click events.
+ */
+ CLICK_TARGETS: [
+ "closeTab",
+ "restoreTab",
+ "restoreAll",
+ "sendReport",
+ ],
+
+ /**
+ * Returns information about this crashed tab.
+ *
+ * @return (Object) An object with the following properties:
+ * title (String):
+ * The title of the page that crashed.
+ * URL (String):
+ * The URL of the page that crashed.
+ */
+ get pageData() {
+ delete this.pageData;
+
+ let URL = document.documentURI;
+ let queryString = URL.replace(/^about:tabcrashed?e=tabcrashed/, "");
+
+ let titleMatch = queryString.match(/d=([^&]*)/);
+ let URLMatch = queryString.match(/u=([^&]*)/);
+
+ return this.pageData = {
+ title: titleMatch && titleMatch[1] ? decodeURIComponent(titleMatch[1]) : "",
+ URL: URLMatch && URLMatch[1] ? decodeURIComponent(URLMatch[1]) : "",
+ };
+ },
+
+ init() {
+ this.MESSAGES.forEach((msg) => addMessageListener(msg, this.receiveMessage.bind(this)));
+ addEventListener("DOMContentLoaded", this);
+
+ document.title = this.pageData.title;
+ },
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "UpdateCount": {
+ this.setMultiple(message.data.count > 1);
+ break;
+ }
+ case "SetCrashReportAvailable": {
+ this.onSetCrashReportAvailable(message);
+ break;
+ }
+ case "CrashReportSent": {
+ this.onCrashReportSent();
+ break;
+ }
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "DOMContentLoaded": {
+ this.onDOMContentLoaded();
+ break;
+ }
+ case "click": {
+ this.onClick(event);
+ break;
+ }
+ case "input": {
+ this.onInput(event);
+ break;
+ }
+ }
+ },
+
+ onDOMContentLoaded() {
+ this.CLICK_TARGETS.forEach((targetID) => {
+ let el = document.getElementById(targetID);
+ el.addEventListener("click", this);
+ });
+
+ // For setting "emailMe" checkbox automatically on email value change.
+ document.getElementById("email").addEventListener("input", this);
+
+ // Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
+ let event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
+ document.dispatchEvent(event);
+
+ sendAsyncMessage("Load");
+ },
+
+ onClick(event) {
+ switch (event.target.id) {
+ case "closeTab": {
+ this.sendMessage("closeTab");
+ break;
+ }
+
+ case "restoreTab": {
+ this.sendMessage("restoreTab");
+ break;
+ }
+
+ case "restoreAll": {
+ this.sendMessage("restoreAll");
+ break;
+ }
+
+ case "sendReport": {
+ this.showCrashReportUI(event.target.checked);
+ break;
+ }
+ }
+ },
+
+ onInput(event) {
+ switch (event.target.id) {
+ case "email": {
+ document.getElementById("emailMe").checked = !!event.target.value;
+ break;
+ }
+ }
+ },
+ /**
+ * After this page tells the parent that it has loaded, the parent
+ * will respond with whether or not a crash report is available. This
+ * method handles that message.
+ *
+ * @param message
+ * The message from the parent, which should contain a data
+ * Object property with the following properties:
+ *
+ * hasReport (bool):
+ * Whether or not there is a crash report.
+ *
+ * sendReport (bool):
+ * Whether or not the the user prefers to send the report
+ * by default.
+ *
+ * includeURL (bool):
+ * Whether or not the user prefers to send the URL of
+ * the tab that crashed.
+ *
+ * emailMe (bool):
+ * Whether or not to send the email address of the user
+ * in the report.
+ *
+ * email (String):
+ * The email address of the user (empty if emailMe is false).
+ *
+ * requestAutoSubmit (bool):
+ * Whether or not we should ask the user to automatically
+ * submit backlogged crash reports.
+ *
+ */
+ onSetCrashReportAvailable(message) {
+ let data = message.data;
+
+ if (data.hasReport) {
+ this.hasReport = true;
+ document.documentElement.classList.add("crashDumpAvailable");
+
+ document.getElementById("sendReport").checked = data.sendReport;
+ document.getElementById("includeURL").checked = data.includeURL;
+
+ if (data.requestEmail) {
+ document.getElementById("requestEmail").hidden = false;
+ document.getElementById("emailMe").checked = data.emailMe;
+ if (data.emailMe) {
+ document.getElementById("email").value = data.email;
+ }
+ }
+
+ this.showCrashReportUI(data.sendReport);
+ } else {
+ this.showCrashReportUI(false);
+ }
+
+ if (data.requestAutoSubmit) {
+ document.getElementById("requestAutoSubmit").hidden = false;
+ }
+
+ let event = new CustomEvent("AboutTabCrashedReady", {bubbles:true});
+ document.dispatchEvent(event);
+ },
+
+ /**
+ * Handler for when the parent reports that the crash report associated
+ * with this about:tabcrashed page has been sent.
+ */
+ onCrashReportSent() {
+ document.documentElement.classList.remove("crashDumpAvailable");
+ document.documentElement.classList.add("crashDumpSubmitted");
+ },
+
+ /**
+ * Toggles the display of the crash report form.
+ *
+ * @param shouldShow (bool)
+ * True if the crash report form should be shown
+ */
+ showCrashReportUI(shouldShow) {
+ let options = document.getElementById("options");
+ options.hidden = !shouldShow;
+ },
+
+ /**
+ * Toggles whether or not the page is one of several visible pages
+ * showing the crash reporter. This controls some of the language
+ * on the page, along with what the "primary" button is.
+ *
+ * @param hasMultiple (bool)
+ * True if there are multiple crash report pages being shown.
+ */
+ setMultiple(hasMultiple) {
+ let main = document.getElementById("main");
+ main.setAttribute("multiple", hasMultiple);
+
+ let restoreTab = document.getElementById("restoreTab");
+
+ // The "Restore All" button has the "primary" class by default, so
+ // we only need to modify the "Restore Tab" button.
+ if (hasMultiple) {
+ restoreTab.classList.remove("primary");
+ } else {
+ restoreTab.classList.add("primary");
+ }
+ },
+
+ /**
+ * Sends a message to the parent in response to the user choosing
+ * one of the actions available on the page. This might also send up
+ * crash report information if the user has chosen to submit a crash
+ * report.
+ *
+ * @param messageName (String)
+ * The message to send to the parent
+ */
+ sendMessage(messageName) {
+ let comments = "";
+ let email = "";
+ let URL = "";
+ let sendReport = false;
+ let emailMe = false;
+ let includeURL = false;
+ let autoSubmit = false;
+
+ if (this.hasReport) {
+ sendReport = document.getElementById("sendReport").checked;
+ if (sendReport) {
+ comments = document.getElementById("comments").value.trim();
+
+ includeURL = document.getElementById("includeURL").checked;
+ if (includeURL) {
+ URL = this.pageData.URL.trim();
+ }
+
+ if (!document.getElementById("requestEmail").hidden) {
+ emailMe = document.getElementById("emailMe").checked;
+ if (emailMe) {
+ email = document.getElementById("email").value.trim();
+ }
+ }
+ }
+ }
+
+ let requestAutoSubmit = document.getElementById("requestAutoSubmit");
+ if (requestAutoSubmit.hidden) {
+ // The checkbox is hidden if the user has already opted in to sending
+ // backlogged crash reports.
+ autoSubmit = true;
+ } else {
+ autoSubmit = document.getElementById("autoSubmit").checked;
+ }
+
+ sendAsyncMessage(messageName, {
+ sendReport,
+ comments,
+ email,
+ emailMe,
+ includeURL,
+ URL,
+ autoSubmit,
+ hasReport: this.hasReport,
+ });
+ },
+};
+
+AboutTabCrashed.init();
diff --git a/basilisk/base/content/aboutTabCrashed.xhtml b/basilisk/base/content/aboutTabCrashed.xhtml
new file mode 100644
index 000000000..8b18bee9c
--- /dev/null
+++ b/basilisk/base/content/aboutTabCrashed.xhtml
@@ -0,0 +1,97 @@
+
+
+
+
+
+ %htmlDTD;
+
+ %globalDTD;
+
+ %brandDTD;
+
+ %tabCrashedDTD;
+]>
+
+
+
+
+
+
+
&tabCrashed.header2;
+
+
+
+
&tabCrashed.offerHelp;
+
&tabCrashed.single.offerHelpMessage;
+
&tabCrashed.multiple.offerHelpMessage;
+
+
+
+
&tabCrashed.requestHelp;
+
&tabCrashed.requestHelpMessage;
+
+
&tabCrashed.requestReport;
+
+
+
+
+
+
+
+
+
+
&tabCrashed.requestAutoSubmit2;
+
+
+
+
+
+
+
+
&tabCrashed.reportSent;
+
+
+
+
+
+
+
+
+
+
diff --git a/basilisk/base/content/abouthealthreport/abouthealth.css b/basilisk/base/content/abouthealthreport/abouthealth.css
new file mode 100644
index 000000000..3dd40fc24
--- /dev/null
+++ b/basilisk/base/content/abouthealthreport/abouthealth.css
@@ -0,0 +1,15 @@
+* {
+ margin: 0;
+ padding: 0;
+}
+
+html, body {
+ height: 100%;
+}
+
+#remote-report {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ display: flex;
+}
diff --git a/basilisk/base/content/abouthealthreport/abouthealth.js b/basilisk/base/content/abouthealthreport/abouthealth.js
new file mode 100644
index 000000000..66cbe16f5
--- /dev/null
+++ b/basilisk/base/content/abouthealthreport/abouthealth.js
@@ -0,0 +1,180 @@
+/* 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/. */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const prefs = new Preferences("datareporting.healthreport.");
+
+const PREF_UNIFIED = "toolkit.telemetry.unified";
+const PREF_REPORTING_URL = "datareporting.healthreport.about.reportUrl";
+
+var healthReportWrapper = {
+ init: function () {
+ let iframe = document.getElementById("remote-report");
+ iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
+ iframe.src = this._getReportURI().spec;
+ prefs.observe("uploadEnabled", this.updatePrefState, healthReportWrapper);
+ },
+
+ uninit: function () {
+ prefs.ignore("uploadEnabled", this.updatePrefState, healthReportWrapper);
+ },
+
+ _getReportURI: function () {
+ let url = Services.urlFormatter.formatURLPref(PREF_REPORTING_URL);
+ return Services.io.newURI(url, null, null);
+ },
+
+ setDataSubmission: function (enabled) {
+ MozSelfSupport.healthReportDataSubmissionEnabled = enabled;
+ this.updatePrefState();
+ },
+
+ updatePrefState: function () {
+ try {
+ let prefs = {
+ enabled: MozSelfSupport.healthReportDataSubmissionEnabled,
+ };
+ healthReportWrapper.injectData("prefs", prefs);
+ }
+ catch (ex) {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PREFS_FAILED);
+ }
+ },
+
+ sendTelemetryPingList: function () {
+ console.log("AboutHealthReport: Collecting Telemetry ping list.");
+ MozSelfSupport.getTelemetryPingList().then((list) => {
+ console.log("AboutHealthReport: Sending Telemetry ping list.");
+ this.injectData("telemetry-ping-list", list);
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Collecting ping list failed: " + ex);
+ });
+ },
+
+ sendTelemetryPingData: function (pingId) {
+ console.log("AboutHealthReport: Collecting Telemetry ping data.");
+ MozSelfSupport.getTelemetryPing(pingId).then((ping) => {
+ console.log("AboutHealthReport: Sending Telemetry ping data.");
+ this.injectData("telemetry-ping-data", {
+ id: pingId,
+ pingData: ping,
+ });
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Loading ping data failed: " + ex);
+ this.injectData("telemetry-ping-data", {
+ id: pingId,
+ error: "error-generic",
+ });
+ });
+ },
+
+ sendCurrentEnvironment: function () {
+ console.log("AboutHealthReport: Sending Telemetry environment data.");
+ MozSelfSupport.getCurrentTelemetryEnvironment().then((environment) => {
+ this.injectData("telemetry-current-environment-data", environment);
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Collecting current environment data failed: " + ex);
+ });
+ },
+
+ sendCurrentPingData: function () {
+ console.log("AboutHealthReport: Sending current Telemetry ping data.");
+ MozSelfSupport.getCurrentTelemetrySubsessionPing().then((ping) => {
+ this.injectData("telemetry-current-ping-data", ping);
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Collecting current ping data failed: " + ex);
+ });
+ },
+
+ injectData: function (type, content) {
+ let report = this._getReportURI();
+
+ // file URIs can't be used for targetOrigin, so we use "*" for this special case
+ // in all other cases, pass in the URL to the report so we properly restrict the message dispatch
+ let reportUrl = report.scheme == "file" ? "*" : report.spec;
+
+ let data = {
+ type: type,
+ content: content
+ }
+
+ let iframe = document.getElementById("remote-report");
+ iframe.contentWindow.postMessage(data, reportUrl);
+ },
+
+ handleRemoteCommand: function (evt) {
+ // Do an origin check to harden against the frame content being loaded from unexpected locations.
+ let allowedPrincipal = Services.scriptSecurityManager.getCodebasePrincipal(this._getReportURI());
+ let targetPrincipal = evt.target.nodePrincipal;
+ if (!allowedPrincipal.equals(targetPrincipal)) {
+ Cu.reportError(`Origin check failed for message "${evt.detail.command}": ` +
+ `target origin is "${targetPrincipal.origin}", expected "${allowedPrincipal.origin}"`);
+ return;
+ }
+
+ switch (evt.detail.command) {
+ case "DisableDataSubmission":
+ this.setDataSubmission(false);
+ break;
+ case "EnableDataSubmission":
+ this.setDataSubmission(true);
+ break;
+ case "RequestCurrentPrefs":
+ this.updatePrefState();
+ break;
+ case "RequestTelemetryPingList":
+ this.sendTelemetryPingList();
+ break;
+ case "RequestTelemetryPingData":
+ this.sendTelemetryPingData(evt.detail.id);
+ break;
+ case "RequestCurrentEnvironment":
+ this.sendCurrentEnvironment();
+ break;
+ case "RequestCurrentPingData":
+ this.sendCurrentPingData();
+ break;
+ default:
+ Cu.reportError("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
+ break;
+ }
+ },
+
+ initRemotePage: function () {
+ let iframe = document.getElementById("remote-report").contentDocument;
+ iframe.addEventListener("RemoteHealthReportCommand",
+ function onCommand(e) { healthReportWrapper.handleRemoteCommand(e); },
+ false);
+ healthReportWrapper.updatePrefState();
+ },
+
+ // error handling
+ ERROR_INIT_FAILED: 1,
+ ERROR_PAYLOAD_FAILED: 2,
+ ERROR_PREFS_FAILED: 3,
+
+ reportFailure: function (error) {
+ let details = {
+ errorType: error,
+ }
+ healthReportWrapper.injectData("error", details);
+ },
+
+ handleInitFailure: function () {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED);
+ },
+
+ handlePayloadFailure: function () {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED);
+ },
+}
+
+window.addEventListener("load", function () { healthReportWrapper.init(); });
+window.addEventListener("unload", function () { healthReportWrapper.uninit(); });
diff --git a/basilisk/base/content/abouthealthreport/abouthealth.xhtml b/basilisk/base/content/abouthealthreport/abouthealth.xhtml
new file mode 100644
index 000000000..464635788
--- /dev/null
+++ b/basilisk/base/content/abouthealthreport/abouthealth.xhtml
@@ -0,0 +1,31 @@
+
+
+
+ %htmlDTD;
+
+ %brandDTD;
+
+ %securityPrefsDTD;
+
+ %aboutHealthReportDTD;
+]>
+
+
+
+