diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
commit | 59203c63bb777a3bacec32fb8830fba33540e809 (patch) | |
tree | 58298e711c0ff0575818c30485b44a2f21bf28a0 /toolkit/mozapps/extensions/content/abuse-report-panel.js | |
parent | Adding upstream version 126.0.1. (diff) | |
download | firefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip |
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/mozapps/extensions/content/abuse-report-panel.js')
-rw-r--r-- | toolkit/mozapps/extensions/content/abuse-report-panel.js | 873 |
1 files changed, 0 insertions, 873 deletions
diff --git a/toolkit/mozapps/extensions/content/abuse-report-panel.js b/toolkit/mozapps/extensions/content/abuse-report-panel.js deleted file mode 100644 index 53e0d76db0..0000000000 --- a/toolkit/mozapps/extensions/content/abuse-report-panel.js +++ /dev/null @@ -1,873 +0,0 @@ -/* 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] */ - -"use strict"; - -ChromeUtils.defineESModuleGetters(this, { - AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs", -}); - -const IS_DIALOG_WINDOW = window.arguments && window.arguments.length; - -let openWebLink = IS_DIALOG_WINDOW - ? window.arguments[0].wrappedJSObject.openWebLink - : url => { - window.windowRoot.ownerGlobal.openWebLinkIn(url, "tab", { - relatedToCurrent: true, - }); - }; - -const showOnAnyType = () => false; -const hideOnAnyType = () => true; -const hideOnAddonTypes = hideForTypes => { - return addonType => hideForTypes.includes(addonType); -}; - -// The reasons string used as a key in this Map is expected to stay in sync -// with the reasons string used in the "abuseReports.ftl" locale file and -// the suggestions templates included in abuse-report-frame.html. -const ABUSE_REASONS = (window.ABUSE_REPORT_REASONS = { - damage: { - isExampleHidden: showOnAnyType, - isReasonHidden: hideOnAddonTypes(["theme"]), - }, - spam: { - isExampleHidden: showOnAnyType, - isReasonHidden: hideOnAddonTypes(["sitepermission"]), - }, - settings: { - hasSuggestions: true, - isExampleHidden: hideOnAnyType, - isReasonHidden: hideOnAddonTypes(["theme", "sitepermission"]), - }, - deceptive: { - isExampleHidden: showOnAnyType, - isReasonHidden: hideOnAddonTypes(["sitepermission"]), - }, - broken: { - hasAddonTypeL10nId: true, - hasAddonTypeSuggestionTemplate: true, - hasSuggestions: true, - isExampleHidden: hideOnAddonTypes(["theme"]), - isReasonHidden: showOnAnyType, - requiresSupportURL: true, - }, - policy: { - hasSuggestions: true, - isExampleHidden: hideOnAnyType, - isReasonHidden: hideOnAddonTypes(["sitepermission"]), - }, - unwanted: { - isExampleHidden: showOnAnyType, - isReasonHidden: hideOnAddonTypes(["theme"]), - }, - other: { - isExampleHidden: hideOnAnyType, - isReasonHidden: showOnAnyType, - }, -}); - -// Maps the reason id to the last version of the related fluent id. -// NOTE: when changing the localized string, increase the `-vN` suffix -// in the abuseReports.ftl fluent file and update this mapping table. -const REASON_L10N_STRING_MAPPING = { - "abuse-report-damage-reason": "abuse-report-damage-reason-v2", - "abuse-report-spam-reason": "abuse-report-spam-reason-v2", - "abuse-report-settings-reason": "abuse-report-settings-reason-v2", - "abuse-report-deceptive-reason": "abuse-report-deceptive-reason-v2", - "abuse-report-broken-reason-extension": - "abuse-report-broken-reason-extension-v2", - "abuse-report-broken-reason-sitepermission": - "abuse-report-broken-reason-sitepermission-v2", - "abuse-report-broken-reason-theme": "abuse-report-broken-reason-theme-v2", - "abuse-report-policy-reason": "abuse-report-policy-reason-v2", - "abuse-report-unwanted-reason": "abuse-report-unwanted-reason-v2", -}; - -function getReasonL10nId(reason, addonType) { - let reasonId = `abuse-report-${reason}-reason`; - // Special case reasons that have a addonType-specific - // l10n id. - if (ABUSE_REASONS[reason].hasAddonTypeL10nId) { - reasonId += `-${addonType}`; - } - // Map the reason to the corresponding versionized fluent string, using the - // mapping table above, if available. - return REASON_L10N_STRING_MAPPING[reasonId] || reasonId; -} - -function getSuggestionsTemplate({ addonType, reason, supportURL }) { - const reasonInfo = ABUSE_REASONS[reason]; - - if ( - !addonType || - !reasonInfo.hasSuggestions || - (reasonInfo.requiresSupportURL && !supportURL) - ) { - return null; - } - - let templateId = `tmpl-suggestions-${reason}`; - // Special case reasons that have a addonType-specific - // suggestion template. - if (reasonInfo.hasAddonTypeSuggestionTemplate) { - templateId += `-${addonType}`; - } - - return document.getElementById(templateId); -} - -// Map of the learnmore links metadata, keyed by link element class. -const LEARNMORE_LINKS = { - ".abuse-policy-learnmore": { - baseURL: "https://www.mozilla.org/%LOCALE%/", - path: "about/legal/report-infringement/", - }, -}; - -// Format links that match the selector in the LEARNMORE_LINKS map -// found in a given container element. -function formatLearnMoreURLs(containerEl) { - for (const [linkClass, linkInfo] of Object.entries(LEARNMORE_LINKS)) { - for (const element of containerEl.querySelectorAll(linkClass)) { - const baseURL = Services.urlFormatter.formatURL(linkInfo.baseURL); - - element.href = baseURL + linkInfo.path; - } - } -} - -// Define a set of getters from a Map<propertyName, selector>. -function defineElementSelectorsGetters(object, propsMap) { - const props = Object.entries(propsMap).reduce((acc, entry) => { - const [name, selector] = entry; - acc[name] = { get: () => object.querySelector(selector) }; - return acc; - }, {}); - Object.defineProperties(object, props); -} - -// Define a set of properties getters and setters for a -// Map<propertyName, attributeName>. -function defineElementAttributesProperties(object, propsMap) { - const props = Object.entries(propsMap).reduce((acc, entry) => { - const [name, attr] = entry; - acc[name] = { - get: () => object.getAttribute(attr), - set: value => { - object.setAttribute(attr, value); - }, - }; - return acc; - }, {}); - Object.defineProperties(object, props); -} - -// Return an object with properties associated to elements -// found using the related selector in the propsMap. -function getElements(containerEl, propsMap) { - return Object.entries(propsMap).reduce((acc, entry) => { - const [name, selector] = entry; - let elements = containerEl.querySelectorAll(selector); - acc[name] = elements.length > 1 ? elements : elements[0]; - return acc; - }, {}); -} - -function dispatchCustomEvent(el, eventName, detail) { - el.dispatchEvent(new CustomEvent(eventName, { detail })); -} - -// This WebComponent extends the li item to represent an abuse report reason -// and it is responsible for: -// - embedding a photon styled radio buttons -// - localizing the reason list item -// - optionally embedding a localized example, positioned -// below the reason label, and adjusts the item height -// accordingly -class AbuseReasonListItem extends HTMLLIElement { - constructor() { - super(); - defineElementAttributesProperties(this, { - addonType: "addon-type", - reason: "report-reason", - checked: "checked", - }); - } - - connectedCallback() { - this.update(); - } - - async update() { - if (this.reason !== "other" && !this.addonType) { - return; - } - - const { reason, checked, addonType } = this; - - this.textContent = ""; - const content = document.importNode(this.template.content, true); - - if (reason) { - const reasonId = `abuse-reason-${reason}`; - const reasonInfo = ABUSE_REASONS[reason] || {}; - - const { labelEl, descriptionEl, radioEl } = getElements(content, { - labelEl: "label", - descriptionEl: ".reason-description", - radioEl: "input[type=radio]", - }); - - labelEl.setAttribute("for", reasonId); - radioEl.id = reasonId; - radioEl.value = reason; - radioEl.checked = !!checked; - - // This reason has a different localized description based on the - // addon type. - document.l10n.setAttributes( - descriptionEl, - getReasonL10nId(reason, addonType) - ); - - // Show the reason example if supported for the addon type. - if (!reasonInfo.isExampleHidden(addonType)) { - const exampleEl = content.querySelector(".reason-example"); - document.l10n.setAttributes( - exampleEl, - `abuse-report-${reason}-example` - ); - exampleEl.hidden = false; - } - } - - formatLearnMoreURLs(content); - - this.appendChild(content); - } - - get template() { - return document.getElementById("tmpl-reason-listitem"); - } -} - -// This WebComponents implements the first step of the abuse -// report submission and embeds a randomized reasons list. -class AbuseReasonsPanel extends HTMLElement { - constructor() { - super(); - defineElementAttributesProperties(this, { - addonType: "addon-type", - }); - } - - connectedCallback() { - this.update(); - } - - update() { - if (!this.isConnected || !this.addonType) { - return; - } - - const { addonType } = this; - - this.textContent = ""; - const content = document.importNode(this.template.content, true); - - const { titleEl, listEl } = getElements(content, { - titleEl: ".abuse-report-title", - listEl: "ul.abuse-report-reasons", - }); - - // Change the title l10n-id if the addon type is theme. - document.l10n.setAttributes(titleEl, `abuse-report-title-${addonType}`); - - // Create the randomized list of reasons. - const reasons = Object.keys(ABUSE_REASONS) - .filter(reason => reason !== "other") - .sort(() => Math.random() - 0.5); - - for (const reason of reasons) { - const reasonInfo = ABUSE_REASONS[reason]; - if (!reasonInfo || reasonInfo.isReasonHidden(addonType)) { - // Skip an extension only reason while reporting a theme. - continue; - } - const item = document.createElement("li", { - is: "abuse-report-reason-listitem", - }); - item.reason = reason; - item.addonType = addonType; - - listEl.prepend(item); - } - - listEl.firstElementChild.checked = true; - formatLearnMoreURLs(content); - - this.appendChild(content); - } - - get template() { - return document.getElementById("tmpl-reasons-panel"); - } -} - -// This WebComponent is responsible for the suggestions, which are: -// - generated based on a template keyed by abuse report reason -// - localized by assigning fluent ids generated from the abuse report reason -// - learn more and extension support url are then generated when the -// specific reason expects it -class AbuseReasonSuggestions extends HTMLElement { - constructor() { - super(); - defineElementAttributesProperties(this, { - extensionSupportURL: "extension-support-url", - reason: "report-reason", - }); - } - - update() { - const { addonType, extensionSupportURL, reason } = this; - - this.textContent = ""; - - let template = getSuggestionsTemplate({ - addonType, - reason, - supportURL: extensionSupportURL, - }); - - if (template) { - let content = document.importNode(template.content, true); - - formatLearnMoreURLs(content); - - let extSupportLink = content.querySelector("a.extension-support-link"); - if (extSupportLink) { - extSupportLink.href = extensionSupportURL; - } - - this.appendChild(content); - this.hidden = false; - } else { - this.hidden = true; - } - } -} - -// This WebComponents implements the last step of the abuse report submission. -class AbuseSubmitPanel extends HTMLElement { - constructor() { - super(); - defineElementAttributesProperties(this, { - addonType: "addon-type", - reason: "report-reason", - extensionSupportURL: "extensionSupportURL", - }); - defineElementSelectorsGetters(this, { - _textarea: "textarea", - _title: ".abuse-reason-title", - _suggestions: "abuse-report-reason-suggestions", - }); - } - - connectedCallback() { - this.render(); - } - - render() { - this.textContent = ""; - this.appendChild(document.importNode(this.template.content, true)); - } - - update() { - if (!this.isConnected || !this.addonType) { - return; - } - const { addonType, reason, _suggestions, _title } = this; - document.l10n.setAttributes(_title, getReasonL10nId(reason, addonType)); - _suggestions.reason = reason; - _suggestions.addonType = addonType; - _suggestions.extensionSupportURL = this.extensionSupportURL; - _suggestions.update(); - } - - clear() { - this._textarea.value = ""; - } - - get template() { - return document.getElementById("tmpl-submit-panel"); - } -} - -// This WebComponent provides the abuse report -class AbuseReport extends HTMLElement { - constructor() { - super(); - this._report = null; - defineElementSelectorsGetters(this, { - _form: "form", - _textarea: "textarea", - _radioCheckedReason: "[type=radio]:checked", - _reasonsPanel: "abuse-report-reasons-panel", - _submitPanel: "abuse-report-submit-panel", - _reasonsPanelButtons: ".abuse-report-reasons-buttons", - _submitPanelButtons: ".abuse-report-submit-buttons", - _iconClose: ".abuse-report-close-icon", - _btnNext: "button.abuse-report-next", - _btnCancel: "button.abuse-report-cancel", - _btnGoBack: "button.abuse-report-goback", - _btnSubmit: "button.abuse-report-submit", - _addonAuthorContainer: ".abuse-report-header .addon-author-box", - _addonIconElement: ".abuse-report-header img.addon-icon", - _addonNameElement: ".abuse-report-header .addon-name", - _linkAddonAuthor: ".abuse-report-header .addon-author-box a.author", - }); - } - - connectedCallback() { - this.render(); - - this.addEventListener("click", this); - - // Start listening to keydown events (to close the modal - // when Escape has been pressed and to handling the keyboard - // navigation). - document.addEventListener("keydown", this); - } - - disconnectedCallback() { - this.textContent = ""; - this.removeEventListener("click", this); - document.removeEventListener("keydown", this); - } - - handleEvent(evt) { - if (!this.isConnected || !this.addon) { - return; - } - - switch (evt.type) { - case "keydown": - if (evt.key === "Escape") { - // Prevent Esc to close the panel if the textarea is - // empty. - if (this.message && !this._submitPanel.hidden) { - return; - } - this.cancel(); - } - if (!IS_DIALOG_WINDOW) { - // Workaround keyboard navigation issues when - // the panel is running in its own dialog window. - this.handleKeyboardNavigation(evt); - } - break; - case "click": - if (evt.target === this._iconClose || evt.target === this._btnCancel) { - // NOTE: clear the focus on the clicked element to ensure that - // -moz-focusring pseudo class is not still set on the element - // when the panel is going to be shown again (See Bug 1560949). - evt.target.blur(); - this.cancel(); - } - if (evt.target === this._btnNext) { - this.switchToSubmitMode(); - } - if (evt.target === this._btnGoBack) { - this.switchToListMode(); - } - if (evt.target === this._btnSubmit) { - this.submit(); - } - if (evt.target.localName === "a") { - evt.preventDefault(); - evt.stopPropagation(); - const url = evt.target.getAttribute("href"); - // Ignore if url is empty. - if (url) { - openWebLink(url); - } - } - break; - } - } - - handleKeyboardNavigation(evt) { - if ( - evt.keyCode !== evt.DOM_VK_TAB || - evt.altKey || - evt.controlKey || - evt.metaKey - ) { - return; - } - - const fm = Services.focus; - const backward = evt.shiftKey; - - const isFirstFocusableElement = el => { - // Also consider the document body as a valid first focusable element. - if (el === document.body) { - return true; - } - // XXXrpl unfortunately there is no way to get the first focusable element - // without asking the focus manager to move focus to it (similar strategy - // is also being used in about:prefereces subdialog.js). - const rv = el == fm.moveFocus(window, null, fm.MOVEFOCUS_FIRST, 0); - fm.setFocus(el, 0); - return rv; - }; - - // If the focus is exiting the panel while navigating - // backward, focus the previous element sibling on the - // Firefox UI. - if (backward && isFirstFocusableElement(evt.target)) { - evt.preventDefault(); - evt.stopImmediatePropagation(); - const chromeWin = window.windowRoot.ownerGlobal; - Services.focus.moveFocus( - chromeWin, - null, - Services.focus.MOVEFOCUS_BACKWARD, - Services.focus.FLAG_BYKEY - ); - } - } - - render() { - this.textContent = ""; - const formTemplate = document.importNode(this.template.content, true); - if (IS_DIALOG_WINDOW) { - this.appendChild(formTemplate); - } else { - // Append the report form inside a modal overlay when the report panel - // is a sub-frame of the about:addons tab. - const modalTemplate = document.importNode( - this.modalTemplate.content, - true - ); - - this.appendChild(modalTemplate); - this.querySelector(".modal-panel-container").appendChild(formTemplate); - - // Add the card styles to the form. - this.querySelector("form").classList.add("card"); - } - } - - async update() { - if (!this.addon) { - return; - } - - const { - addonId, - addonType, - _addonAuthorContainer, - _addonIconElement, - _addonNameElement, - _linkAddonAuthor, - _reasonsPanel, - _submitPanel, - } = this; - - // Ensure that the first step of the abuse submission is the one - // currently visible. - this.switchToListMode(); - - // Cancel the abuse report if the addon is not an extension or theme. - if (!AbuseReporter.isSupportedAddonType(addonType)) { - Cu.reportError( - new Error( - `Closing abuse report panel on unexpected addon type: ${addonType}` - ) - ); - this.cancel(); - return; - } - - _addonNameElement.textContent = this.addonName; - - if (this.authorName) { - _linkAddonAuthor.href = this.authorURL || this.homepageURL; - _linkAddonAuthor.textContent = this.authorName; - document.l10n.setAttributes( - _linkAddonAuthor.parentNode, - "abuse-report-addon-authored-by", - { "author-name": this.authorName } - ); - _addonAuthorContainer.hidden = false; - } else { - _addonAuthorContainer.hidden = true; - } - - _addonIconElement.setAttribute("src", this.iconURL); - - _reasonsPanel.addonType = this.addonType; - _reasonsPanel.update(); - - _submitPanel.addonType = this.addonType; - _submitPanel.reason = this.reason; - _submitPanel.extensionSupportURL = this.supportURL; - _submitPanel.update(); - - this.focus(); - - dispatchCustomEvent(this, "abuse-report:updated", { - addonId, - panel: "reasons", - }); - } - - setAbuseReport(abuseReport) { - this._report = abuseReport; - // Clear the textarea from any previously entered content. - this._submitPanel.clear(); - - if (abuseReport) { - this.update(); - this.hidden = false; - } else { - this.hidden = true; - } - } - - focus() { - if (!this.isConnected || !this.addon) { - return; - } - if (this._reasonsPanel.hidden) { - const { _textarea } = this; - _textarea.focus(); - _textarea.select(); - } else { - const { _radioCheckedReason } = this; - if (_radioCheckedReason) { - _radioCheckedReason.focus(); - } - } - } - - cancel() { - if (!this.isConnected || !this.addon) { - return; - } - this._report = null; - dispatchCustomEvent(this, "abuse-report:cancel"); - } - - submit() { - if (!this.isConnected || !this.addon) { - return; - } - this._report.setMessage(this.message); - this._report.setReason(this.reason); - dispatchCustomEvent(this, "abuse-report:submit", { - addonId: this.addonId, - report: this._report, - }); - } - - switchToSubmitMode() { - if (!this.isConnected || !this.addon) { - return; - } - this._submitPanel.reason = this.reason; - this._submitPanel.update(); - this._reasonsPanel.hidden = true; - this._reasonsPanelButtons.hidden = true; - this._submitPanel.hidden = false; - this._submitPanelButtons.hidden = false; - // Adjust the focused element when switching to the submit panel. - this.focus(); - dispatchCustomEvent(this, "abuse-report:updated", { - addonId: this.addonId, - panel: "submit", - }); - } - - switchToListMode() { - if (!this.isConnected || !this.addon) { - return; - } - this._submitPanel.hidden = true; - this._submitPanelButtons.hidden = true; - this._reasonsPanel.hidden = false; - this._reasonsPanelButtons.hidden = false; - // Adjust the focused element when switching back to the list of reasons. - this.focus(); - dispatchCustomEvent(this, "abuse-report:updated", { - addonId: this.addonId, - panel: "reasons", - }); - } - - get addon() { - return this._report?.addon; - } - - get addonId() { - return this.addon?.id; - } - - get addonName() { - return this.addon?.name; - } - - get addonType() { - // TODO(Bug 1789718): Remove after the deprecated XPIProvider-based - // implementation is also removed. - if (this.addon?.type === "sitepermission-deprecated") { - return "sitepermission"; - } - return this.addon?.type; - } - - get addonCreator() { - return this.addon?.creator; - } - - get homepageURL() { - return this.addon?.homepageURL || this.authorURL || ""; - } - - get authorName() { - // The author name may be missing on some of the test extensions - // (or for temporarily installed add-ons). - return this.addonCreator?.name || ""; - } - - get authorURL() { - return this.addonCreator?.url || ""; - } - - get iconURL() { - if (this.addonType === "sitepermission") { - return "chrome://mozapps/skin/extensions/category-sitepermission.svg"; - } - return ( - this.addon?.iconURL || - // Some extensions (e.g. static theme addons) may not have an icon, - // and so we fallback to use the generic extension icon. - "chrome://mozapps/skin/extensions/extensionGeneric.svg" - ); - } - - get supportURL() { - let url = this.addon?.supportURL || this.homepageURL || ""; - if (!url && this.addonType === "sitepermission" && this.addon?.siteOrigin) { - return this.addon.siteOrigin; - } - return url; - } - - get message() { - return this._form.elements.message.value; - } - - get reason() { - return this._form.elements.reason.value; - } - - get modalTemplate() { - return document.getElementById("tmpl-modal"); - } - - get template() { - return document.getElementById("tmpl-abuse-report"); - } -} - -customElements.define("abuse-report-reason-listitem", AbuseReasonListItem, { - extends: "li", -}); -customElements.define( - "abuse-report-reason-suggestions", - AbuseReasonSuggestions -); -customElements.define("abuse-report-reasons-panel", AbuseReasonsPanel); -customElements.define("abuse-report-submit-panel", AbuseSubmitPanel); -customElements.define("addon-abuse-report", AbuseReport); - -// The panel has been opened in a new dialog window. -if (IS_DIALOG_WINDOW) { - // CSS customizations when panel is in its own window - // (vs. being an about:addons subframe). - document.documentElement.className = "dialog-window"; - - const { report, deferredReport, deferredReportPanel } = - window.arguments[0].wrappedJSObject; - - window.addEventListener( - "unload", - () => { - // If the window has been closed resolve the deferredReport - // promise and reject the deferredReportPanel one, in case - // they haven't been resolved yet. - deferredReport.resolve({ userCancelled: true }); - deferredReportPanel.reject(new Error("report dialog closed")); - }, - { once: true } - ); - - document.l10n.setAttributes( - document.querySelector("head > title"), - "abuse-report-dialog-title", - { - "addon-name": report.addon.name, - } - ); - - const el = document.querySelector("addon-abuse-report"); - el.addEventListener("abuse-report:submit", () => { - deferredReport.resolve({ - userCancelled: false, - report, - }); - }); - el.addEventListener( - "abuse-report:cancel", - () => { - // Resolve the report panel deferred (in case the report - // has been cancelled automatically before it has been fully - // rendered, e.g. in case of non-supported addon types). - deferredReportPanel.resolve(el); - // Resolve the deferred report as cancelled. - deferredReport.resolve({ userCancelled: true }); - }, - { once: true } - ); - - // Adjust window size (if needed) once the fluent strings have been - // added to the document and the document has been flushed. - el.addEventListener( - "abuse-report:updated", - async () => { - const form = document.querySelector("form"); - await document.l10n.translateFragment(form); - const { scrollWidth, scrollHeight } = await window.promiseDocumentFlushed( - () => form - ); - // Resolve promiseReportPanel once the panel completed the initial render - // (used in tests). - deferredReportPanel.resolve(el); - if ( - window.innerWidth !== scrollWidth || - window.innerHeight !== scrollHeight - ) { - const width = window.outerWidth - window.innerWidth + scrollWidth; - const height = window.outerHeight - window.innerHeight + scrollHeight; - window.resizeTo(width, height); - } - }, - { once: true } - ); - el.setAbuseReport(report); -} |