/* 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 { AddonManagerListenerHandler, DiscoveryAPI, } from "../aboutaddons-utils.mjs"; const { AddonManager } = ChromeUtils.importESModule( "resource://gre/modules/AddonManager.sys.mjs" ); class RecommendedAddonList extends HTMLElement { connectedCallback() { if (this.isConnected) { this.loadCardsIfNeeded(); this.updateCardsWithAddonManager(); } AddonManagerListenerHandler.addListener(this); } disconnectedCallback() { AddonManagerListenerHandler.removeListener(this); } get type() { return this.getAttribute("type"); } /** * Set the add-on type for this list. This will be used to filter the add-ons * that are displayed. * * Must be set prior to the first render. * * @param {string} val The type to filter on. */ set type(val) { this.setAttribute("type", val); } get hideInstalled() { return this.hasAttribute("hide-installed"); } /** * Set whether installed add-ons should be hidden from the list. If false, * installed add-ons will be shown with a "Manage" button, otherwise they * will be hidden. * * Must be set prior to the first render. * * @param {boolean} val Whether to show installed add-ons. */ set hideInstalled(val) { this.toggleAttribute("hide-installed", val); } getCardById(addonId) { for (let card of this.children) { if (card.addonId === addonId) { return card; } } return null; } setAddonForCard(card, addon) { card.setAddon(addon); let wasHidden = card.hidden; card.hidden = this.hideInstalled && addon; if (wasHidden != card.hidden) { let eventName = card.hidden ? "card-hidden" : "card-shown"; this.dispatchEvent(new CustomEvent(eventName, { detail: { card } })); } } /** * Whether the client ID should be preferred. This is disabled for themes * since they don't use the telemetry data and don't show the TAAR notice. */ get preferClientId() { return !this.type || this.type == "extension"; } async updateCardsWithAddonManager() { let cards = Array.from(this.children); let addonIds = cards.map(card => card.addonId); let addons = await AddonManager.getAddonsByIDs(addonIds); for (let [i, card] of cards.entries()) { let addon = addons[i]; this.setAddonForCard(card, addon); if (addon) { // Already installed, move card to end. this.append(card); } } } async loadCardsIfNeeded() { // Use promise as guard. Also used by tests to detect when load completes. if (!this.cardsReady) { this.cardsReady = this._loadCards(); } return this.cardsReady; } async _loadCards() { let recommendedAddons; try { recommendedAddons = await DiscoveryAPI.getResults(this.preferClientId); } catch (e) { return; } let frag = document.createDocumentFragment(); for (let addon of recommendedAddons) { if (this.type && addon.type != this.type) { continue; } let card = document.createElement("recommended-addon-card"); card.setDiscoAddon(addon); frag.append(card); } this.append(frag); await this.updateCardsWithAddonManager(); } /** * AddonManager listener events. */ onInstalled(addon) { let card = this.getCardById(addon.id); if (card) { this.setAddonForCard(card, addon); } } onUninstalled(addon) { let card = this.getCardById(addon.id); if (card) { this.setAddonForCard(card, null); } } } customElements.define("recommended-addon-list", RecommendedAddonList);