/* 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/. */ /* eslint max-len: ["error", 80] */ /* import-globals-from aboutaddonsCommon.js */ /* exported openAbuseReport */ /** * This script is part of the HTML about:addons page and it provides some * helpers used for the Abuse Reporting submission (and related message bars). */ const { AbuseReporter } = ChromeUtils.importESModule( "resource://gre/modules/AbuseReporter.sys.mjs" ); // Message Bars definitions. const ABUSE_REPORT_MESSAGE_BARS = { // Idle message-bar (used while the submission is still ongoing). submitting: { id: "submitting", actions: ["cancel"] }, // Submitted report message-bar. submitted: { id: "submitted", actionAddonTypeSuffix: true, actions: ["remove", "keep"], dismissable: true, }, // Submitted report message-bar (with no remove actions). "submitted-no-remove-action": { id: "submitted-noremove", dismissable: true, }, // Submitted report and remove addon message-bar. "submitted-and-removed": { id: "removed", addonTypeSuffix: true, dismissable: true, }, // The "aborted report" message bar is rendered as a generic informative one, // because aborting a report is triggered by a user choice. ERROR_ABORTED_SUBMIT: { id: "aborted", type: "generic", dismissable: true, }, // Errors message bars. ERROR_ADDON_NOTFOUND: { id: "error", type: "error", dismissable: true, }, ERROR_CLIENT: { id: "error", type: "error", dismissable: true, }, ERROR_NETWORK: { id: "error", actions: ["retry", "cancel"], type: "error", }, ERROR_RECENT_SUBMIT: { id: "error-recent-submit", actions: ["retry", "cancel"], type: "error", }, ERROR_SERVER: { id: "error", actions: ["retry", "cancel"], type: "error", }, ERROR_UNKNOWN: { id: "error", actions: ["retry", "cancel"], type: "error", }, }; async function openAbuseReport({ addonId, reportEntryPoint }) { try { const reportDialog = await AbuseReporter.openDialog( addonId, reportEntryPoint, window.docShell.chromeEventHandler ); // Warn the user before the about:addons tab while an // abuse report dialog is still open, and close the // report dialog if the user choose to close the related // about:addons tab. const beforeunloadListener = evt => evt.preventDefault(); const unloadListener = () => reportDialog.close(); const clearUnloadListeners = () => { window.removeEventListener("beforeunload", beforeunloadListener); window.removeEventListener("unload", unloadListener); }; window.addEventListener("beforeunload", beforeunloadListener); window.addEventListener("unload", unloadListener); reportDialog.promiseReport .then( report => { if (report) { submitReport({ report }); } }, err => { Cu.reportError( `Unexpected abuse report panel error: ${err} :: ${err.stack}` ); reportDialog.close(); } ) .then(clearUnloadListeners); } catch (err) { // Log the detailed error to the browser console. Cu.reportError(err); document.dispatchEvent( new CustomEvent("abuse-report:create-error", { detail: { addonId, addon: err.addon, errorType: err.errorType, }, }) ); } } window.openAbuseReport = openAbuseReport; // Helper function used to create abuse report message bars in the // HTML about:addons page. function createReportMessageBar( definitionId, { addonId, addonName, addonType }, { onclose, onaction } = {} ) { const getMessageL10n = id => `abuse-report-messagebar-${id}`; const getActionL10n = action => getMessageL10n(`action-${action}`); const barInfo = ABUSE_REPORT_MESSAGE_BARS[definitionId]; if (!barInfo) { throw new Error(`message-bar definition not found: ${definitionId}`); } const { id, dismissable, actions, type } = barInfo; const messageEl = document.createElement("span"); // The message element includes an addon-name span (also filled by // Fluent), which can be used to apply custom styles to the addon name // included in the message bar (if needed). const addonNameEl = document.createElement("span"); addonNameEl.setAttribute("data-l10n-name", "addon-name"); messageEl.append(addonNameEl); // TODO(Bug 1789718): Remove after the deprecated XPIProvider-based // implementation is also removed. const mappingAddonType = addonType === "sitepermission-deprecated" ? "sitepermission" : addonType; document.l10n.setAttributes( messageEl, getMessageL10n(barInfo.addonTypeSuffix ? `${id}-${mappingAddonType}` : id), { "addon-name": addonName || addonId } ); const barActions = actions ? actions.map(action => { // Some of the message bars require a different per addonType // Fluent id for their actions. const actionId = barInfo.actionAddonTypeSuffix ? `${action}-${mappingAddonType}` : action; const buttonEl = document.createElement("button"); buttonEl.addEventListener("click", () => onaction && onaction(action)); document.l10n.setAttributes(buttonEl, getActionL10n(actionId)); return buttonEl; }) : []; const messagebar = document.createElement("message-bar"); messagebar.setAttribute("type", type || "generic"); if (dismissable) { messagebar.setAttribute("dismissable", ""); } messagebar.append(messageEl, ...barActions); messagebar.addEventListener("message-bar:close", onclose, { once: true }); document.getElementById("abuse-reports-messages").append(messagebar); document.dispatchEvent( new CustomEvent("abuse-report:new-message-bar", { detail: { definitionId, messagebar }, }) ); return messagebar; } async function submitReport({ report }) { const { addon } = report; const addonId = addon.id; const addonName = addon.name; const addonType = addon.type; // Ensure that the tab that originated the report dialog is selected // when the user is submitting the report. const { gBrowser } = window.windowRoot.ownerGlobal; if (gBrowser && gBrowser.getTabForBrowser) { let tab = gBrowser.getTabForBrowser(window.docShell.chromeEventHandler); gBrowser.selectedTab = tab; } // Create a message bar while we are still submitting the report. const mbSubmitting = createReportMessageBar( "submitting", { addonId, addonName, addonType }, { onaction: action => { if (action === "cancel") { report.abort(); mbSubmitting.remove(); } }, } ); try { await report.submit(); mbSubmitting.remove(); // Create a submitted message bar when the submission has been // successful. let barId; if ( !(addon.permissions & AddonManager.PERM_CAN_UNINSTALL) && !isPending(addon, "uninstall") ) { // Do not offer remove action if the addon can't be uninstalled. barId = "submitted-no-remove-action"; } else if (report.reportEntryPoint === "uninstall") { // With reportEntryPoint "uninstall" a specific message bar // is going to be used. barId = "submitted-and-removed"; } else { // All the other reportEntryPoint ("menu" and "toolbar_context_menu") // use the same kind of message bar. barId = "submitted"; } const mbInfo = createReportMessageBar( barId, { addonId, addonName, addonType, }, { onaction: action => { mbInfo.remove(); // action "keep" doesn't require any further action, // just handle "remove". if (action === "remove") { report.addon.uninstall(true); } }, } ); } catch (err) { // Log the complete error in the console. console.error("Error submitting abuse report for", addonId, err); mbSubmitting.remove(); // The report has a submission error, create a error message bar which // may optionally allow the user to retry to submit the same report. const barId = err.errorType in ABUSE_REPORT_MESSAGE_BARS ? err.errorType : "ERROR_UNKNOWN"; const mbError = createReportMessageBar( barId, { addonId, addonName, addonType, }, { onaction: action => { mbError.remove(); switch (action) { case "retry": submitReport({ report }); break; case "cancel": report.abort(); break; } }, } ); } } document.addEventListener("abuse-report:submit", ({ detail }) => { submitReport(detail); }); document.addEventListener("abuse-report:create-error", ({ detail }) => { const { addonId, addon, errorType } = detail; const barId = errorType in ABUSE_REPORT_MESSAGE_BARS ? errorType : "ERROR_UNKNOWN"; createReportMessageBar(barId, { addonId, addonName: addon && addon.name, addonType: addon && addon.type, }); });