blob: a281e59920c104c16f84e2be34e003e3a0003a52 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {ModuleIdName} from '../new_tab_page.mojom-webui.js';
import {NewTabPageProxy} from '../new_tab_page_proxy.js';
import type {Module, ModuleDescriptor} from './module_descriptor.js';
import {descriptors} from './module_descriptors.js';
/**
* @fileoverview The module registry holds the descriptors of NTP modules and
* provides management function such as instantiating the local module UIs.
*/
let instance: ModuleRegistry|null = null;
export class ModuleRegistry {
static getInstance(): ModuleRegistry {
return instance || (instance = new ModuleRegistry(descriptors));
}
static setInstance(newInstance: ModuleRegistry) {
instance = newInstance;
}
private descriptors_: ModuleDescriptor[];
/** Creates a registry populated with a list of descriptors. */
constructor(descriptors: ModuleDescriptor[]) {
this.descriptors_ = descriptors;
}
/**
* Initializes enabled modules as reported by `getModulesIdNames` excluding
* those that have been disabled for the current profile and returns the
* initialized modules.
* @param timeout Timeout in milliseconds after which initialization of a
* particular module aborts.
*/
async initializeModules(timeout: number): Promise<Module[]> {
const modulesIdNames: ModuleIdName[] =
(await NewTabPageProxy.getInstance().handler.getModulesIdNames()).data;
return this.initializeModulesHavingIds(
modulesIdNames.map(m => m.id), timeout);
}
/**
* Initializes a given list of modules based on the provided module ids.
* Serves as a convenience method for cases where the caller already knows the
* desired list of module ids to load.
*
* @param moduleIds A list of module ids to be leveraged when determining the
* modules to be initialized.
* @param timeout Timeout in milliseconds after which initialization of a
* particular module aborts.
*/
async initializeModulesHavingIds(modulesIds: string[], timeout: number):
Promise<Module[]> {
// Capture updateDisabledModules -> setDisabledModules round trip in a
// promise for convenience.
const disabledIds = await new Promise<string[]>((resolve, _) => {
const callbackRouter = NewTabPageProxy.getInstance().callbackRouter;
const listenerId = callbackRouter.setDisabledModules.addListener(
(all: boolean, ids: string[]) => {
callbackRouter.removeListener(listenerId);
resolve(all ? this.descriptors_.map(({id}) => id) : ids);
});
NewTabPageProxy.getInstance().handler.updateDisabledModules();
});
const descriptorsMap: Map<string, ModuleDescriptor> =
new Map(this.descriptors_.map(d => [d.id, d]));
const descriptors: ModuleDescriptor[] =
modulesIds.filter(id => !disabledIds.includes(id))
.map(id => descriptorsMap.get(id)!);
// Modules may have an updated order, e.g. because of drag&drop or a Finch
// param. Apply the updated order such that modules without a specified
// order (e.g. because they were just enabled or launched) land at the
// bottom of the list.
const orderedIds =
(await NewTabPageProxy.getInstance().handler.getModulesOrder())
.moduleIds;
if (orderedIds.length > 0) {
descriptors.sort((a, b) => {
const aHasOrder = orderedIds.includes(a.id);
const bHasOrder = orderedIds.includes(b.id);
if (aHasOrder && bHasOrder) {
// Apply order.
return orderedIds.indexOf(a.id) - orderedIds.indexOf(b.id);
}
if (!aHasOrder && bHasOrder) {
return 1; // Move b up.
}
if (aHasOrder && !bHasOrder) {
return -1; // Move a up.
}
return 0; // Keep current order.
});
}
const elements = await Promise.all(
descriptors.map(d => d.initialize(timeout, /*onNtpLoad=*/ true)));
return elements.map((e, i) => ({elements: e, descriptor: descriptors[i]}))
.filter(m => !!m.elements)
.map(m => (({
elements: Array.isArray(m.elements) ? m.elements :
[m.elements],
descriptor: m.descriptor,
}) as Module))
.filter(m => m.elements.length !== 0);
}
/**
* Initializes a module based on the provided module id.
* Serves as a convenience method for cases where the caller already knows the
* desired module id to load.
*
* @param moduleId A module id to be leveraged when determining the
* module to be initialized.
* @param timeout Timeout in milliseconds after which initialization of a
* the module aborts.
*/
async initializeModuleById(id: string, timeout: number):
Promise<Module|null> {
const descriptor = this.descriptors_.find(d => d.id === id);
if (!descriptor) {
console.error('Missing descriptor for module id ', id);
return null;
}
const elements = await descriptor.initialize(timeout, /*onNtpLoad=*/ false);
if (!elements) {
return null;
}
return {
elements: Array.isArray(elements) ? elements : [elements],
descriptor: descriptor,
} as Module;
}
}