blob: b40ea153d2b9dcb815fcc674e8f0050a0ba6a0d6 [file] [log] [blame]
/**
* @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 '../../../scripts/bundled-polymer.js';
import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
import {importHref} from '../../../scripts/import-href.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {htmlTemplate} from './gr-endpoint-decorator_html.js';
import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
const INIT_PROPERTIES_TIMEOUT_MS = 10000;
/** @extends Polymer.Element */
class GrEndpointDecorator extends GestureEventListeners(
LegacyElementMixin(
PolymerElement)) {
static get template() { return htmlTemplate; }
static get is() { return 'gr-endpoint-decorator'; }
static get properties() {
return {
name: String,
/** @type {!Map} */
_domHooks: {
type: Map,
value() { return new Map(); },
},
/**
* This map prevents importing the same endpoint twice.
* Without caching, if a plugin is loaded after the loaded plugins
* callback fires, it will be imported twice and appear twice on the page.
*
* @type {!Map}
*/
_initializedPlugins: {
type: Map,
value() { return new Map(); },
},
};
}
/** @override */
detached() {
super.detached();
for (const [el, domHook] of this._domHooks) {
domHook.handleInstanceDetached(el);
}
pluginEndpoints.onDetachedEndpoint(this.name, this._endpointCallBack);
}
/**
* @suppress {checkTypes}
*/
_import(url) {
return new Promise((resolve, reject) => {
importHref(url, resolve, reject);
});
}
_initDecoration(name, plugin, slot) {
const el = document.createElement(name);
return this._initProperties(el, plugin,
this.getContentChildren().find(
el => el.nodeName !== 'GR-ENDPOINT-PARAM'))
.then(el => {
const slotEl = slot ?
dom(this).querySelector(`gr-endpoint-slot[name=${slot}]`) :
null;
if (slot && slotEl) {
slotEl.parentNode.insertBefore(el, slotEl.nextSibling);
} else {
this._appendChild(el);
}
return el;
});
}
_initReplacement(name, plugin) {
this.getContentChildNodes()
.filter(node => node.nodeName !== 'GR-ENDPOINT-PARAM')
.forEach(node => node.remove());
const el = document.createElement(name);
return this._initProperties(el, plugin).then(
el => this._appendChild(el));
}
_getEndpointParams() {
return Array.from(
dom(this).querySelectorAll('gr-endpoint-param'));
}
/**
* @param {!Element} el
* @param {!Object} plugin
* @param {!Element=} opt_content
* @return {!Promise<Element>}
*/
_initProperties(el, plugin, opt_content) {
el.plugin = plugin;
if (opt_content) {
el.content = opt_content;
}
const expectProperties = this._getEndpointParams().map(paramEl => {
const helper = plugin.attributeHelper(paramEl);
const paramName = paramEl.getAttribute('name');
return helper.get('value').then(
value => helper.bind('value',
value => plugin.attributeHelper(el).set(paramName, value))
);
});
let timeoutId;
const timeout = new Promise(
resolve => timeoutId = setTimeout(() => {
console.warn(
'Timeout waiting for endpoint properties initialization: ' +
`plugin ${plugin.getPluginName()}, endpoint ${this.name}`);
}, INIT_PROPERTIES_TIMEOUT_MS));
return Promise.race([timeout, Promise.all(expectProperties)])
.then(() => {
clearTimeout(timeoutId);
return el;
});
}
_appendChild(el) {
return dom(this.root).appendChild(el);
}
_initModule({moduleName, plugin, type, domHook, slot}) {
const name = plugin.getPluginName() + '.' + moduleName;
if (this._initializedPlugins.get(name)) {
return;
}
let initPromise;
switch (type) {
case 'decorate':
initPromise = this._initDecoration(moduleName, plugin, slot);
break;
case 'replace':
initPromise = this._initReplacement(moduleName, plugin);
break;
}
if (!initPromise) {
console.warn('Unable to initialize module ' + name);
}
this._initializedPlugins.set(name, true);
initPromise.then(el => {
domHook.handleInstanceAttached(el);
this._domHooks.set(el, domHook);
});
}
/** @override */
ready() {
super.ready();
this._endpointCallBack = this._initModule.bind(this);
pluginEndpoints.onNewEndpoint(this.name, this._endpointCallBack);
if (this.name) {
pluginLoader.awaitPluginsLoaded()
.then(() => Promise.all(
pluginEndpoints.getPlugins(this.name).map(
pluginUrl => this._import(pluginUrl)))
)
.then(() =>
pluginEndpoints
.getDetails(this.name)
.forEach(this._initModule, this)
);
}
}
}
customElements.define(GrEndpointDecorator.is, GrEndpointDecorator);