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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { openAbuseReport } from "./abuse-reports.mjs";
import {
AddonManagerListenerHandler,
PREF_UI_LASTCATEGORY,
UPDATES_RECENT_TIMESPAN,
checkForUpdates,
getUpdateInstall,
isAddonOptionsUIAllowed,
isManualUpdate,
isPending,
} from "./aboutaddons-utils.mjs";
import { gViewController, loadView } 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 = {};
ChromeUtils.defineESModuleGetters(lazy, {
recordDetailsViewTelemetry: "chrome://global/content/ml/Utils.sys.mjs",
});
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"LIST_RECOMMENDATIONS_ENABLED",
"extensions.htmlaboutaddons.recommendations.enabled",
false
);
const L10N_ID_MAPPING = {
"theme-disabled-heading": "theme-disabled-heading2",
};
function getL10nIdMapping(id) {
return L10N_ID_MAPPING[id] || id;
}
// Define views
gViewController.defineView("list", async type => {
if (!AddonManager.hasAddonType(type)) {
return null;
}
let frag = document.createDocumentFragment();
let list = document.createElement("addon-list");
list.type = type;
let sections = [
{
headingId: type + "-enabled-heading",
sectionClass: `${type}-enabled-section`,
filterFn: addon =>
!addon.hidden && addon.isActive && !isPending(addon, "uninstall"),
},
];
const disabledAddonsFilterFn = addon =>
!addon.hidden && !addon.isActive && !isPending(addon, "uninstall");
sections.push({
headingId: getL10nIdMapping(`${type}-disabled-heading`),
sectionClass: `${type}-disabled-section`,
filterFn: disabledAddonsFilterFn,
});
// Show the colorway, forced-colors and smart window theme notices only
// in themes list view.
if (type === "theme") {
const colorwayNotice = document.createElement("colorway-removal-notice");
frag.appendChild(colorwayNotice);
const forcedColorsNotice = document.createElement("forced-colors-notice");
frag.appendChild(forcedColorsNotice);
const smartWindowNotice = document.createElement(
"smartwindow-themes-notice"
);
frag.appendChild(smartWindowNotice);
}
list.setSections(sections);
frag.appendChild(list);
// Show recommendations for themes and extensions.
if (
lazy.LIST_RECOMMENDATIONS_ENABLED &&
(type == "extension" || type == "theme")
) {
let elementName =
type == "extension"
? "recommended-extensions-section"
: "recommended-themes-section";
let recommendations = document.createElement(elementName);
// Start loading the recommendations. This can finish after the view load
// event is sent.
recommendations.render();
frag.appendChild(recommendations);
}
await list.render();
return frag;
});
gViewController.defineView("detail", async param => {
let [id, selectedTab] = param.split("/");
let addon = await AddonManager.getAddonByID(id);
if (!addon) {
return null;
}
if (addon.type === "mlmodel") {
lazy.recordDetailsViewTelemetry(addon);
}
let card = document.createElement("addon-card");
// Ensure the category for this add-on type is selected.
document.querySelector("categories-box").selectType(addon.type);
// Go back to the list view when the add-on is removed.
card.addEventListener("remove", () =>
gViewController.loadView(`list/${addon.type}`)
);
card.setAddon(addon);
card.expand();
await card.render();
if (selectedTab === "preferences" && (await isAddonOptionsUIAllowed(addon))) {
card.showPrefs();
}
return card;
});
gViewController.defineView("updates", async param => {
let list = document.createElement("addon-list");
list.type = "all";
if (param == "available") {
list.setSections([
{
headingId: "available-updates-heading",
filterFn: addon => {
// Filter the addons visible in the updates view using the same
// criteria that is being used to compute the counter on the
// available updates category button badge (updateAvailableCount).
const install = getUpdateInstall(addon);
return install && isManualUpdate(install);
},
},
]);
list.listenForUpdates();
} else if (param == "recent") {
list.sortByFn = (a, b) => {
if (a.updateDate > b.updateDate) {
return -1;
}
if (a.updateDate < b.updateDate) {
return 1;
}
return 0;
};
let updateLimit = new Date() - UPDATES_RECENT_TIMESPAN;
list.setSections([
{
headingId: "recent-updates-heading",
filterFn: addon =>
!addon.hidden && addon.updateDate && addon.updateDate > updateLimit,
},
]);
} else {
throw new Error(`Unknown updates view ${param}`);
}
await list.render();
return list;
});
gViewController.defineView("discover", async () => {
let discopane = document.createElement("discovery-pane");
discopane.render();
await document.l10n.translateFragment(discopane);
return discopane;
});
gViewController.defineView("shortcuts", async extensionId => {
// Force the extension category to be selected, in the case of a reload,
// restart, or if the view was opened from another category's page.
document.querySelector("categories-box").selectType("extension");
let view = document.createElement("addon-shortcuts");
if (extensionId && extensionId !== "shortcuts") {
view.setAttribute("extension-id", extensionId);
}
await view.render();
await document.l10n.translateFragment(view);
return view;
});
// Expose window globals expected by tests and external callers.
window.loadView = loadView;
window.gViewController = gViewController;
window.openAbuseReport = openAbuseReport;
window.getL10nIdMapping = getL10nIdMapping;
window.checkForUpdates = checkForUpdates;
/**
* Called when about:addons is loaded.
*/
async function initialize() {
window.addEventListener(
"unload",
() => {
// Clear out the document so the disconnectedCallback will trigger
// properly and all of the custom elements can cleanup.
document.body.textContent = "";
AddonManagerListenerHandler.shutdown();
},
{ once: true }
);
// Init UI and view management
gViewController.initialize(document.getElementById("main"));
document.querySelector("categories-box").initialize();
AddonManagerListenerHandler.startup();
// browser.js may call loadView here if it expects an EM-loaded notification
gViewController.notifyEMLoaded();
// Select an initial view if no listener has set one so far
if (!gViewController.currentViewId) {
if (history.state) {
// If there is a history state to restore then use that
await gViewController.renderState(history.state);
} else {
// Fallback to the last category or first valid category view otherwise.
await gViewController.loadView(
Services.prefs.getStringPref(
PREF_UI_LASTCATEGORY,
gViewController.defaultViewId
)
);
}
}
}
window.promiseInitialized = new Promise(resolve => {
window.addEventListener(
"load",
() => {
initialize().then(resolve);
},
{ once: true }
);
});