| // 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; |
| } |
| } |