/* 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/. */ import { AboutAddonsHTMLElement, formatUTMParams, getOptionsType, getUpdateInstall, hasPermission, isAddonOptionsUIAllowed, isAllowedInPrivateBrowsing, nl2br, } from "../aboutaddons-utils.mjs"; import { ScrollOffsets } from "../view-controller.mjs"; const { AddonManager } = ChromeUtils.importESModule( "resource://gre/modules/AddonManager.sys.mjs" ); const { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); const lazy = {}; XPCOMUtils.defineLazyPreferenceGetter( lazy, "DATA_COLLECTION_PERMISSIONS_ENABLED", "extensions.dataCollectionPermissions.enabled", false ); export class AddonDetails extends AboutAddonsHTMLElement { static get markup() { return ` `; } connectedCallback() { if (!this.children.length) { this.render(); } this.deck.addEventListener("view-changed", this); this.descriptionShowMoreButton.addEventListener("click", this); } disconnectedCallback() { this.inlineOptions.destroyBrowser(); this.deck.removeEventListener("view-changed", this); this.descriptionShowMoreButton.removeEventListener("click", this); } handleEvent(e) { if (e.type == "view-changed" && e.target == this.deck) { switch (this.deck.selectedViewName) { case "release-notes": { let releaseNotes = this.querySelector("update-release-notes"); let uri = this.releaseNotesUri; if (uri) { releaseNotes.loadForUri(uri); } break; } case "preferences": if (getOptionsType(this.addon) == "inline") { this.inlineOptions.ensureBrowserCreated(); } break; } // When a details view is rendered again, the default details view is // unconditionally shown. So if any other tab is selected, do not save // the current scroll offset, but start at the top of the page instead. ScrollOffsets.canRestore = this.deck.selectedViewName === "details"; } else if ( e.type == "click" && e.target == this.descriptionShowMoreButton ) { this.toggleDescription(); } } onInstalled() { let policy = WebExtensionPolicy.getByID(this.addon.id); let extension = policy && policy.extension; if (extension && extension.startupReason === "ADDON_UPGRADE") { // Ensure the options browser is recreated when a new version starts. this.extensionShutdown(); this.extensionStartup(); } } onDisabled() { this.extensionShutdown(); } onEnabled() { this.extensionStartup(); } extensionShutdown() { this.inlineOptions.destroyBrowser(); } extensionStartup() { if (this.deck.selectedViewName === "preferences") { this.inlineOptions.ensureBrowserCreated(); } } toggleDescription() { this.descriptionCollapsed = !this.descriptionCollapsed; this.descriptionWrapper.classList.toggle( "addon-detail-description-collapse", this.descriptionCollapsed ); this.descriptionShowMoreButton.hidden = false; document.l10n.setAttributes( this.descriptionShowMoreButton, this.descriptionCollapsed ? "addon-detail-description-expand" : "addon-detail-description-collapse" ); } get releaseNotesUri() { let { releaseNotesURI } = getUpdateInstall(this.addon) || this.addon; return releaseNotesURI; } setAddon(addon) { this.addon = addon; } update() { let { addon } = this; // Hide tab buttons that won't have any content. let getButtonByName = name => this.tabGroup.querySelector(`[name="${name}"]`); let permsBtn = getButtonByName("permissions"); permsBtn.hidden = addon.type != "extension"; let notesBtn = getButtonByName("release-notes"); notesBtn.hidden = !this.releaseNotesUri; let prefsBtn = getButtonByName("preferences"); prefsBtn.hidden = getOptionsType(addon) !== "inline"; if (prefsBtn.hidden) { if (this.deck.selectedViewName === "preferences") { this.deck.selectedViewName = "details"; } } else { isAddonOptionsUIAllowed(addon).then(allowed => { prefsBtn.hidden = !allowed; }); } // Override the deck button string when the feature is enabled, which isn't // the case by default for now. if (lazy.DATA_COLLECTION_PERMISSIONS_ENABLED) { permsBtn.setAttribute("data-l10n-id", "permissions-data-addon-button"); } // Hide the tab group if "details" is the only visible button. let tabGroupButtons = this.tabGroup.querySelectorAll(".tab-button"); this.tabGroup.hidden = Array.from(tabGroupButtons).every(button => { return button.name == "details" || button.hidden; }); // Show the update check button if necessary. The button might not exist if // the add-on doesn't support updates. let updateButton = this.querySelector('[action="update-check"]'); if (updateButton) { updateButton.hidden = this.addon.updateInstall || AddonManager.shouldAutoUpdate(this.addon); } // Set the value for auto updates. let inputs = this.querySelectorAll(".addon-detail-row-updates input"); for (let input of inputs) { input.checked = input.value == addon.applyBackgroundUpdates; } } renderDescription(addon) { this.descriptionWrapper = this.querySelector( ".addon-detail-description-wrapper" ); this.descriptionContents = this.querySelector(".addon-detail-description"); this.descriptionShowMoreButton = this.querySelector( ".addon-detail-description-toggle" ); if (addon.getFullDescription) { this.descriptionContents.appendChild(addon.getFullDescription(document)); } else if (addon.fullDescription) { this.descriptionContents.appendChild(nl2br(addon.fullDescription)); } this.descriptionCollapsed = false; requestAnimationFrame(() => { const remSize = parseFloat( getComputedStyle(document.documentElement).fontSize ); const { height } = this.descriptionContents.getBoundingClientRect(); // collapse description if there are too many lines,i.e. height > (20 rem) if (height > 20 * remSize) { this.toggleDescription(); } }); } updateQuarantinedDomainsUserAllowed() { const { addon } = this; let quarantinedDomainsUserAllowedRow = this.querySelector( ".addon-detail-row-quarantined-domains" ); if (addon.canChangeQuarantineIgnored) { quarantinedDomainsUserAllowedRow.hidden = false; quarantinedDomainsUserAllowedRow.nextElementSibling.hidden = false; quarantinedDomainsUserAllowedRow.querySelector( `[value="${addon.quarantineIgnoredByUser ? 1 : 0}"]` ).checked = true; } else { quarantinedDomainsUserAllowedRow.hidden = true; quarantinedDomainsUserAllowedRow.nextElementSibling.hidden = true; } } async render() { let { addon } = this; if (!addon) { throw new Error("addon-details must be initialized by setAddon"); } this.textContent = ""; this.appendChild(AddonDetails.fragment); this.deck = this.querySelector("named-deck"); this.tabGroup = this.querySelector(".tab-group"); // Set the add-on for the permissions section. this.permissionsList = this.querySelector("addon-permissions-list"); this.permissionsList.setAddon(addon); // Set the add-on for the sitepermissions section. this.sitePermissionsList = this.querySelector("addon-sitepermissions-list"); if (addon.type == "sitepermission") { this.sitePermissionsList.setAddon(addon); } this.querySelector(".addon-detail-sitepermissions").hidden = addon.type !== "sitepermission"; // Set the add-on for the mlmodel details. if (addon.type == "mlmodel") { this.mlModelDetails = this.querySelector("addon-mlmodel-details"); this.mlModelDetails.setAddon(addon); this.querySelector(".addon-detail-mlmodel").hidden = false; } // Set the add-on for the preferences section. this.inlineOptions = this.querySelector("inline-options-browser"); this.inlineOptions.setAddon(addon); // Full description. this.renderDescription(addon); this.querySelector(".addon-detail-contribute").hidden = !addon.contributionURL; this.querySelector(".addon-detail-row-updates").hidden = !hasPermission( addon, "upgrade" ); if (addon.type != "extension") { // Don't show any private browsing related section for non-extension // addon types, because not relevant or they are either always allowed // (e.g. static themes). // // TODO(Bug 1799090): introduce ad-hoc UI for "sitepermission" addon type. } else if (addon.incognito == "not_allowed") { let pbRowNotAllowed = this.querySelector( ".addon-detail-row-private-browsing-disallowed" ); pbRowNotAllowed.hidden = false; pbRowNotAllowed.nextElementSibling.hidden = false; } else if (!hasPermission(addon, "change-privatebrowsing")) { let pbRowRequired = this.querySelector( ".addon-detail-row-private-browsing-required" ); pbRowRequired.hidden = false; pbRowRequired.nextElementSibling.hidden = false; } else { let pbRow = this.querySelector(".addon-detail-row-private-browsing"); pbRow.hidden = false; pbRow.nextElementSibling.hidden = false; let isAllowed = await isAllowedInPrivateBrowsing(addon); pbRow.querySelector(`[value="${isAllowed ? 1 : 0}"]`).checked = true; } this.updateQuarantinedDomainsUserAllowed(); // Author. let creatorRow = this.querySelector(".addon-detail-row-author"); if (addon.creator) { let link = creatorRow.querySelector("a"); link.hidden = !addon.creator.url; if (link.hidden) { creatorRow.appendChild(new Text(addon.creator.name)); } else { link.href = formatUTMParams( "addons-manager-user-profile-link", addon.creator.url ); link.target = "_blank"; link.textContent = addon.creator.name; } } else { creatorRow.hidden = true; } // Version. Don't show a version for LWTs. let version = this.querySelector(".addon-detail-row-version"); if (addon.version && !/@personas\.mozilla\.org/.test(addon.id)) { version.appendChild(new Text(addon.version)); } else { version.hidden = true; } // Last updated. let updateDate = this.querySelector(".addon-detail-row-lastUpdated"); if (addon.updateDate) { let lastUpdated = addon.updateDate.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric", }); updateDate.appendChild(new Text(lastUpdated)); } else { updateDate.hidden = true; } // Homepage. let homepageRow = this.querySelector(".addon-detail-row-homepage"); if (addon.homepageURL) { let homepageURL = homepageRow.querySelector("a"); homepageURL.href = addon.homepageURL; homepageURL.textContent = addon.homepageURL; } else { homepageRow.hidden = true; } // Rating. let ratingRow = this.querySelector(".addon-detail-row-rating"); if (addon.reviewURL) { ratingRow.querySelector("moz-five-star").rating = addon.averageRating; let reviews = ratingRow.querySelector("a"); reviews.href = formatUTMParams( "addons-manager-reviews-link", addon.reviewURL ); document.l10n.setAttributes(reviews, "addon-detail-reviews-link", { numberOfReviews: addon.reviewCount, }); } else { ratingRow.hidden = true; } this.update(); } showPrefs() { if (getOptionsType(this.addon) == "inline") { this.deck.selectedViewName = "preferences"; this.inlineOptions.ensureBrowserCreated(); } } } customElements.define("addon-details", AddonDetails);