| /** |
| * @license |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import {pluginLoader} from './gr-plugin-loader.js'; |
| import {importHref} from '../../../scripts/import-href.js'; |
| |
| /** @constructor */ |
| export class GrPluginEndpoints { |
| constructor() { |
| this._endpoints = {}; |
| this._callbacks = {}; |
| this._dynamicPlugins = {}; |
| this._importedUrls = new Set(); |
| } |
| |
| onNewEndpoint(endpoint, callback) { |
| if (!this._callbacks[endpoint]) { |
| this._callbacks[endpoint] = []; |
| } |
| this._callbacks[endpoint].push(callback); |
| } |
| |
| onDetachedEndpoint(endpoint, callback) { |
| if (this._callbacks[endpoint]) { |
| this._callbacks[endpoint] = this._callbacks[endpoint].filter( |
| cb => cb !== callback |
| ); |
| } |
| } |
| |
| _getOrCreateModuleInfo(plugin, opts) { |
| const {endpoint, slot, type, moduleName, domHook} = opts; |
| const existingModule = this._endpoints[endpoint].find( |
| info => |
| info.plugin === plugin && |
| info.moduleName === moduleName && |
| info.domHook === domHook && |
| info.slot === slot |
| ); |
| if (existingModule) { |
| return existingModule; |
| } else { |
| const newModule = { |
| moduleName, |
| plugin, |
| pluginUrl: plugin._url, |
| type, |
| domHook, |
| slot, |
| }; |
| this._endpoints[endpoint].push(newModule); |
| return newModule; |
| } |
| } |
| |
| /** |
| * Register a plugin to an endpoint. |
| * |
| * Dynamic plugins are registered to a specific prefix, such as |
| * 'change-list-header'. These plugins are then fetched by prefix to determine |
| * which endpoints to dynamically add to the page. |
| * |
| * @param {Object} plugin |
| * @param {Object} opts |
| */ |
| registerModule(plugin, opts) { |
| const {endpoint, dynamicEndpoint} = opts; |
| if (dynamicEndpoint) { |
| if (!this._dynamicPlugins[dynamicEndpoint]) { |
| this._dynamicPlugins[dynamicEndpoint] = new Set(); |
| } |
| this._dynamicPlugins[dynamicEndpoint].add(endpoint); |
| } |
| if (!this._endpoints[endpoint]) { |
| this._endpoints[endpoint] = []; |
| } |
| const moduleInfo = this._getOrCreateModuleInfo(plugin, opts); |
| if (pluginLoader.arePluginsLoaded() && this._callbacks[endpoint]) { |
| this._callbacks[endpoint].forEach(callback => callback(moduleInfo)); |
| } |
| } |
| |
| getDynamicEndpoints(dynamicEndpoint) { |
| const plugins = this._dynamicPlugins[dynamicEndpoint]; |
| if (!plugins) return []; |
| return Array.from(plugins); |
| } |
| |
| /** |
| * Get detailed information about modules registered with an extension |
| * endpoint. |
| * |
| * @param {string} name Endpoint name. |
| * @param {?{ |
| * type: (string|undefined), |
| * moduleName: (string|undefined) |
| * }} opt_options |
| * @return {!Array<{ |
| * moduleName: string, |
| * plugin: Plugin, |
| * pluginUrl: String, |
| * type: EndpointType, |
| * domHook: !Object |
| * }>} |
| */ |
| getDetails(name, opt_options) { |
| const type = opt_options && opt_options.type; |
| const moduleName = opt_options && opt_options.moduleName; |
| if (!this._endpoints[name]) { |
| return []; |
| } |
| return this._endpoints[name].filter( |
| item => |
| (!type || item.type === type) && |
| (!moduleName || moduleName == item.moduleName) |
| ); |
| } |
| |
| /** |
| * Get detailed module names for instantiating at the endpoint. |
| * |
| * @param {string} name Endpoint name. |
| * @param {?{ |
| * type: (string|undefined), |
| * moduleName: (string|undefined) |
| * }} opt_options |
| * @return {!Array<string>} |
| */ |
| getModules(name, opt_options) { |
| const modulesData = this.getDetails(name, opt_options); |
| if (!modulesData.length) { |
| return []; |
| } |
| return modulesData.map(m => m.moduleName); |
| } |
| |
| /** |
| * Get plugin URLs with element and module definitions. |
| * |
| * @param {string} name Endpoint name. |
| * @param {?{ |
| * type: (string|undefined), |
| * moduleName: (string|undefined) |
| * }} opt_options |
| * @return {!Array<!URL>} |
| */ |
| getPlugins(name, opt_options) { |
| const modulesData = this.getDetails(name, opt_options); |
| if (!modulesData.length) { |
| return []; |
| } |
| return Array.from(new Set(modulesData.map(m => m.pluginUrl))); |
| } |
| |
| importUrl(pluginUrl) { |
| let timerId; |
| return Promise |
| .race([ |
| new Promise((resolve, reject) => { |
| this._importedUrls.add(pluginUrl.href); |
| importHref(pluginUrl, resolve, reject); |
| }), |
| // Timeout after 3s |
| new Promise(r => timerId = setTimeout(r, 3000)), |
| ]) |
| .finally(() => { |
| if (timerId) clearTimeout(timerId); |
| }); |
| } |
| |
| /** |
| * Get plugin URLs with element and module definitions. |
| * |
| * @param {string} name Endpoint name. |
| * @param {?{ |
| * type: (string|undefined), |
| * moduleName: (string|undefined) |
| * }} opt_options |
| * @return {!Array<!Promise<void>>} |
| */ |
| getAndImportPlugins(name, opt_options) { |
| return Promise.all( |
| this.getPlugins(name, opt_options).map(pluginUrl => { |
| if (this._importedUrls.has(pluginUrl.href)) { |
| return Promise.resolve(); |
| } |
| |
| // TODO: we will deprecate html plugins entirely |
| // for now, keep the original behavior and import |
| // only for html ones |
| if (pluginUrl && pluginUrl.pathname.endsWith('.html')) { |
| return this.importUrl(pluginUrl); |
| } else { |
| return Promise.resolve(); |
| } |
| }) |
| ); |
| } |
| } |
| |
| // TODO(dmfilippov): Convert to service and add to appContext |
| export let pluginEndpoints = new GrPluginEndpoints(); |
| export function _testOnly_resetEndpoints() { |
| pluginEndpoints = new GrPluginEndpoints(); |
| } |