Source code
Revision control
Copy as Markdown
Other Tools
/* 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
import {
AddonManagerListenerHandler,
getUpdateInstall,
isManualUpdate,
PREF_UI_LASTCATEGORY,
} from "../aboutaddons-utils.mjs";
import { gViewController } from "../view-controller.mjs";
const { AddonManager } = ChromeUtils.importESModule(
"resource://gre/modules/AddonManager.sys.mjs"
);
// Create the button-group element so it gets loaded.
document.createElement("button-group");
class CategoriesBox extends customElements.get("button-group") {
constructor() {
super();
// This will resolve when the initial category states have been set from
// our cached prefs. This is intended for use in testing to verify that we
// are caching the previous state.
this.promiseRendered = new Promise(resolve => {
this._resolveRendered = resolve;
});
}
handleEvent(e) {
if (e.target == document && e.type == "view-selected") {
const { type, param } = e.detail;
return;
}
if (e.target == this && e.type == "button-group:key-selected") {
this.activeChild.load();
return;
}
if (e.type == "click") {
const button = e.target.closest("[viewid]");
if (button) {
button.load();
return;
}
}
// Forward the unhandled events to the button-group custom element.
super.handleEvent(e);
}
disconnectedCallback() {
document.removeEventListener("view-selected", this);
this.removeEventListener("button-group:key-selected", this);
this.removeEventListener("click", this);
AddonManagerListenerHandler.removeListener(this);
super.disconnectedCallback();
}
async initialize() {
let hiddenTypes = new Set([]);
for (let button of this.children) {
let { defaultHidden, name } = button;
button.hidden =
!button.isVisible || (defaultHidden && this.shouldHideCategory(name));
if (defaultHidden && AddonManager.hasAddonType(name)) {
hiddenTypes.add(name);
}
}
let hiddenUpdated;
if (hiddenTypes.size) {
hiddenUpdated = this.updateHiddenCategories(Array.from(hiddenTypes));
}
this.updateAvailableCount();
document.addEventListener("view-selected", this);
this.addEventListener("button-group:key-selected", this);
this.addEventListener("click", this);
AddonManagerListenerHandler.addListener(this);
this._resolveRendered();
await hiddenUpdated;
}
shouldHideCategory(name) {
return Services.prefs.getBoolPref(`extensions.ui.${name}.hidden`, true);
}
setShouldHideCategory(name, hide) {
Services.prefs.setBoolPref(`extensions.ui.${name}.hidden`, hide);
}
getButtonByName(name) {
return this.querySelector(`[name="${name}"]`);
}
get selectedChild() {
return this._selectedChild;
}
set selectedChild(node) {
if (node && this.contains(node)) {
if (this._selectedChild) {
this._selectedChild.selected = false;
}
this._selectedChild = node;
this._selectedChild.selected = true;
}
}
select(viewId) {
let button = this.querySelector(`[viewid="${viewId}"]`);
if (button) {
this.activeChild = button;
this.selectedChild = button;
button.hidden = false;
Services.prefs.setStringPref(PREF_UI_LASTCATEGORY, viewId);
}
}
selectType(type) {
}
onInstalled(addon) {
let button = this.getButtonByName(addon.type);
if (button) {
button.hidden = false;
this.setShouldHideCategory(addon.type, false);
}
this.updateAvailableCount();
}
onInstallStarted(install) {
this.onInstalled(install);
}
onNewInstall() {
this.updateAvailableCount();
}
onInstallPostponed() {
this.updateAvailableCount();
}
onInstallCancelled() {
this.updateAvailableCount();
}
async updateAvailableCount() {
// Note: This list includes new installs and updates, potentially multiple
let installs = await AddonManager.getAllInstalls();
let addonIdsWithUpdate = new Set();
for (const install of installs) {
if (isManualUpdate(install) && install.existingAddon) {
// Note: install.existingAddon points to the existing addon at the time
// of the update check, which is not necessarily the current version.
const addon = await AddonManager.getAddonByID(install.existingAddon.id);
if (
addon &&
getUpdateInstall(addon) === install &&
Services.vc.compare(install.version, addon.version) > 0
) {
addonIdsWithUpdate.add(addon.id);
}
}
}
const count = addonIdsWithUpdate.size;
let availableButton = this.getButtonByName("available-updates");
availableButton.hidden = !availableButton.selected && count == 0;
availableButton.badgeCount = count;
}
async updateHiddenCategories(types) {
let hiddenTypes = new Set(types);
let getAddons = AddonManager.getAddonsByTypes(types);
let getInstalls = AddonManager.getInstallsByTypes(types);
for (let addon of await getAddons) {
if (addon.hidden) {
continue;
}
this.onInstalled(addon);
hiddenTypes.delete(addon.type);
if (!hiddenTypes.size) {
return;
}
}
for (let install of await getInstalls) {
if (
install.existingAddon ||
install.state == AddonManager.STATE_AVAILABLE
) {
continue;
}
this.onInstalled(install);
hiddenTypes.delete(install.type);
if (!hiddenTypes.size) {
return;
}
}
for (let type of hiddenTypes) {
let button = this.getButtonByName(type);
if (button.selected) {
// Cancel the load if this view should be hidden.
gViewController.resetState();
}
this.setShouldHideCategory(type, true);
button.hidden = true;
}
}
}
customElements.define("categories-box", CategoriesBox);