diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /toolkit/crashreporter/content | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | toolkit/crashreporter/content/crashes.css | 74 | ||||
-rw-r--r-- | toolkit/crashreporter/content/crashes.html | 72 | ||||
-rw-r--r-- | toolkit/crashreporter/content/crashes.js | 291 |
3 files changed, 437 insertions, 0 deletions
diff --git a/toolkit/crashreporter/content/crashes.css b/toolkit/crashreporter/content/crashes.css new file mode 100644 index 0000000000..509ef32189 --- /dev/null +++ b/toolkit/crashreporter/content/crashes.css @@ -0,0 +1,74 @@ +/* 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/. */ + +:root { + font-family: sans-serif; + margin: 40px auto; + min-width: 30em; + max-width: 60em; +} + +table { + width: 100%; + padding-bottom: 2em; +} + +.float-end { + float: inline-end; +} +.hidden { + display: none; +} +.table-title-container { + align-items: center; + display: flex; + justify-content: space-between; +} +.wide-button { + display: block; + min-height: 32px; + padding-inline: 30px; +} +.submitting { + background-image: url(chrome://global/skin/icons/loading.png); + background-position: center; + background-repeat: no-repeat; + background-size: 16px; +} +.submitting .submit-crash-button-label { + display: none; +} +.failed-to-submit { + color: #ca8695; +} + +a.button-as-link { + appearance: none; + min-height: 30px; + color: var(--in-content-text-color) !important; + border: 1px solid var(--in-content-box-border-color) !important; + border-radius: 2px; + background-color: var(--in-content-page-background); + line-height: 30px; + margin: 4px 8px; + /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */ + font-size: 1em; +} +a.button-as-link:hover { + background-color: var(--in-content-box-background-hover) !important; + text-decoration: none; +} +h2.lighter-font-weight { + font-weight: lighter; +} + +th { + text-align: start; +} + +@media (min-resolution: 1.1dppx) { + .submitting { + background-image: url(chrome://global/skin/icons/loading@2x.png); + } +} diff --git a/toolkit/crashreporter/content/crashes.html b/toolkit/crashreporter/content/crashes.html new file mode 100644 index 0000000000..bd8455c9b1 --- /dev/null +++ b/toolkit/crashreporter/content/crashes.html @@ -0,0 +1,72 @@ +<!-- 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/. --> + +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'"> + <meta charset="utf-8"> + <link rel="localization" href="crashreporter/aboutcrashes.ftl"> + <link rel="stylesheet" href="chrome://global/content/crashes.css"> + <link rel="stylesheet" media="screen, projection" href="chrome://global/skin/in-content/common.css"> + <script src="chrome://global/content/crashes.js"></script> + <title data-l10n-id="crash-reports-title"></title> + </head> + + <body> + <p id="noConfig" class="hidden" data-l10n-id="no-config-label"></p> + <p id="noSubmittedReports" class="hidden" data-l10n-id="no-reports-label"></p> + + <div id="reportListUnsubmitted" class="hidden"> + <div class="table-title-container"> + <h2 class="lighter-font-weight" data-l10n-id="crashes-unsubmitted-label"></h2> + <button id="submitAllUnsubmittedReports" class="submit-button wide-button" data-l10n-id="submit-all-button-label"></button> + <button id="clearUnsubmittedReports" class="wide-button" data-l10n-id="delete-button-label"></button> + </div> + <table> + <thead> + <tr> + <th data-l10n-id="id-heading"></th> + <th data-l10n-id="date-crashed-heading"></th> + </tr> + </thead> + <tbody id="unsubmitted"></tbody> + </table> + </div> + + <div id="reportListSubmitted" class="hidden"> + <div class="table-title-container"> + <h2 class="lighter-font-weight" data-l10n-id="crashes-submitted-label"></h2> + <button id="clearSubmittedReports" class="wide-button" data-l10n-id="delete-button-label"></button> + </div> + <table> + <thead> + <tr> + <th data-l10n-id="id-heading"></th> + <th data-l10n-id="date-submitted-heading"></th> + </tr> + </thead> + <tbody id="submitted"></tbody> + </table> + </div> + </body> + + <template id="crashReportRow"> + <tr> + <td class="crash-id"></td> + <td></td> + <td class="float-end"></td> + </tr> + </template> + + <template id="crashSubmitButton"> + <button class="submit-button wide-button"> + <span class="submit-crash-button-label" data-l10n-id="submit-crash-button-label"></span> + </button> + </template> + + <template id="viewCrashLink"> + <a class="crash-link button-as-link wide-button" data-l10n-id="view-crash-button-label"></a> + </template> +</html> diff --git a/toolkit/crashreporter/content/crashes.js b/toolkit/crashreporter/content/crashes.js new file mode 100644 index 0000000000..06013ab577 --- /dev/null +++ b/toolkit/crashreporter/content/crashes.js @@ -0,0 +1,291 @@ +/* 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/. */ + +let reportURL; + +const { CrashReports } = ChromeUtils.import( + "resource://gre/modules/CrashReports.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); + +ChromeUtils.defineModuleGetter( + this, + "CrashSubmit", + "resource://gre/modules/CrashSubmit.jsm" +); + +document.addEventListener("DOMContentLoaded", () => { + populateReportLists(); + document + .getElementById("clearUnsubmittedReports") + .addEventListener("click", () => { + clearUnsubmittedReports().catch(Cu.reportError); + }); + document + .getElementById("submitAllUnsubmittedReports") + .addEventListener("click", () => { + submitAllUnsubmittedReports().catch(Cu.reportError); + }); + document + .getElementById("clearSubmittedReports") + .addEventListener("click", () => { + clearSubmittedReports().catch(Cu.reportError); + }); +}); + +const buildID = Services.appinfo.appBuildID; + +/** + * Adds the crash reports with submission buttons and links + * to the unsubmitted and submitted crash report lists. + * If breakpad.reportURL is not set, displays a misconfiguration message + * instead. + */ +function populateReportLists() { + try { + reportURL = Services.prefs.getCharPref("breakpad.reportURL"); + // Ignore any non http/https urls + if (!/^https?:/i.test(reportURL)) { + reportURL = null; + } + } catch (e) { + reportURL = null; + } + if (!reportURL) { + document.getElementById("noConfig").classList.remove("hidden"); + return; + } + + const reports = CrashReports.getReports(); + const dateFormatter = new Services.intl.DateTimeFormat(undefined, { + timeStyle: "short", + dateStyle: "short", + }); + reports.forEach(report => + addReportRow(report.pending, report.id, report.date, dateFormatter) + ); + showAppropriateSections(); +} + +/** + * Adds a crash report with the appropriate submission button + * or viewing link to the unsubmitted or submitted report list + * based on isPending. + * + * @param {Boolean} isPending whether the crash is up for submission + * @param {String} id the unique id of the crash report + * @param {Date} date either the date of crash or date of submission + * @param {Object} dateFormatter formatter for presenting dates to users + */ +function addReportRow(isPending, id, date, dateFormatter) { + const rowTemplate = document.getElementById("crashReportRow"); + const row = document + .importNode(rowTemplate.content, true) + .querySelector("tr"); + row.id = id; + + const cells = row.querySelectorAll("td"); + cells[0].appendChild(document.createTextNode(id)); + cells[1].appendChild(document.createTextNode(dateFormatter.format(date))); + + if (isPending) { + const buttonTemplate = document.getElementById("crashSubmitButton"); + const button = document + .importNode(buttonTemplate.content, true) + .querySelector("button"); + const buttonText = button.querySelector("span"); + button.addEventListener("click", () => + submitPendingReport(id, row, button, buttonText, dateFormatter) + ); + cells[2].appendChild(button); + document.getElementById("unsubmitted").appendChild(row); + } else { + const linkTemplate = document.getElementById("viewCrashLink"); + const link = document + .importNode(linkTemplate.content, true) + .querySelector("a"); + link.href = `${reportURL}${id}`; + cells[2].appendChild(link); + document.getElementById("submitted").appendChild(row); + } +} + +/** + * Shows or hides each of the unsubmitted and submitted report list + * based on whether they contain at least one crash report. + * If hidden, the submitted report list is replaced by a message + * indicating that no crash reports have been submitted. + */ +function showAppropriateSections() { + let hasUnsubmitted = + document.getElementById("unsubmitted").childElementCount > 0; + document + .getElementById("reportListUnsubmitted") + .classList.toggle("hidden", !hasUnsubmitted); + + let hasSubmitted = document.getElementById("submitted").childElementCount > 0; + document + .getElementById("reportListSubmitted") + .classList.toggle("hidden", !hasSubmitted); + document + .getElementById("noSubmittedReports") + .classList.toggle("hidden", hasSubmitted); +} + +/** + * Changes the provided button to display a spinner. Then, tries to submit the + * crash report for the provided id. On success, removes the crash report from + * the list of unsubmitted crash reports and adds a new crash report to the list + * of submitted crash reports. On failure, changes the provided button to display + * a red error message. + * + * @param {String} reportId the unique id of the crash report + * @param {HTMLTableRowElement} row the table row of the crash report + * @param {HTMLButtonElement} button the button pressed to start the submission + * @param {HTMLSpanElement} buttonText the text inside the pressed button + * @param {Object} dateFormatter formatter for presenting dates to users + */ +function submitPendingReport(reportId, row, button, buttonText, dateFormatter) { + button.classList.add("submitting"); + document.getElementById("submitAllUnsubmittedReports").disabled = true; + CrashSubmit.submit(reportId, { noThrottle: true }) + .then( + remoteCrashID => { + document.getElementById("unsubmitted").removeChild(row); + const report = CrashReports.getReports().filter( + report => report.id === remoteCrashID + ); + addReportRow(false, remoteCrashID, report.date, dateFormatter); + showAppropriateSections(); + dispatchEvent("CrashSubmitSucceeded"); + }, + () => { + button.classList.remove("submitting"); + button.classList.add("failed-to-submit"); + document.l10n.setAttributes( + buttonText, + "submit-crash-button-failure-label" + ); + dispatchEvent("CrashSubmitFailed"); + } + ) + .finally(() => { + document.getElementById("submitAllUnsubmittedReports").disabled = false; + }); +} + +/** + * Deletes unsubmitted and old crash reports from the user's device. + * Then, hides the list of unsubmitted crash reports. + */ +async function clearUnsubmittedReports() { + const [title, description] = await document.l10n.formatValues([ + { id: "delete-confirm-title" }, + { id: "delete-unsubmitted-description" }, + ]); + if (!Services.prompt.confirm(window, title, description)) { + return; + } + + await cleanupFolder(CrashReports.pendingDir.path); + await clearOldReports(); + document.getElementById("reportListUnsubmitted").classList.add("hidden"); +} + +/** + * Submits all the pending crash reports and removes all pending reports from pending reports list + * and add them to submitted crash reports. + */ +async function submitAllUnsubmittedReports() { + for ( + var i = 0; + i < document.getElementById("unsubmitted").childNodes.length; + i++ + ) { + document + .getElementById("unsubmitted") + .childNodes[i].cells[2].childNodes[0].click(); + } +} + +/** + * Deletes submitted and old crash reports from the user's device. + * Then, hides the list of submitted crash reports. + */ +async function clearSubmittedReports() { + const [title, description] = await document.l10n.formatValues([ + { id: "delete-confirm-title" }, + { id: "delete-submitted-description" }, + ]); + if (!Services.prompt.confirm(window, title, description)) { + return; + } + + await cleanupFolder( + CrashReports.submittedDir.path, + async entry => entry.name.startsWith("bp-") && entry.name.endsWith(".txt") + ); + await clearOldReports(); + document.getElementById("reportListSubmitted").classList.add("hidden"); + document.getElementById("noSubmittedReports").classList.remove("hidden"); +} + +/** + * Deletes old crash reports from the user's device. + */ +async function clearOldReports() { + const oneYearAgo = Date.now() - 31586000000; + await cleanupFolder(CrashReports.reportsDir.path, async entry => { + if ( + !entry.name.startsWith("InstallTime") || + entry.name == "InstallTime" + buildID + ) { + return false; + } + + let date = entry.winLastWriteDate; + if (!date) { + const stat = await OS.File.stat(entry.path); + date = stat.lastModificationDate; + } + return date < oneYearAgo; + }); +} + +/** + * Deletes files from the user's device at the specified path + * that match the provided filter. + * + * @param {String} path the directory location to delete form + * @param {Function} filter function taking in a file entry and + * returning whether to delete the file + */ +async function cleanupFolder(path, filter) { + const iterator = new OS.File.DirectoryIterator(path); + try { + await iterator.forEach(async entry => { + if (!filter || (await filter(entry))) { + await OS.File.remove(entry.path); + } + }); + } catch (e) { + if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile) { + throw e; + } + } finally { + iterator.close(); + } +} + +/** + * Dispatches an event with the specified name. + * + * @param {String} name the name of the event + */ +function dispatchEvent(name) { + const event = document.createEvent("Events"); + event.initEvent(name, true, false); + document.dispatchEvent(event); +} |