diff options
Diffstat (limited to 'polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js')
-rw-r--r-- | polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js | 394 |
1 files changed, 320 insertions, 74 deletions
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js index ac4129f430..36a428d966 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js @@ -1,35 +1,49 @@ -// Copyright (C) 2016 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. +/** + * @license + * Copyright (C) 2016 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. + */ (function(window) { 'use strict'; - const warnNotSupported = function(opt_name) { - console.warn('Plugin API method ' + (opt_name || '') + ' is not supported'); - }; - /** * Hash of loaded and installed plugins, name to Plugin object. */ - const plugins = {}; + const _plugins = {}; - const stubbedMethods = ['_loadedGwt', 'screen', 'settingsScreen', 'panel']; - const GWT_PLUGIN_STUB = {}; - for (const name of stubbedMethods) { - GWT_PLUGIN_STUB[name] = warnNotSupported.bind(null, name); - } + /** + * Array of plugin URLs to be loaded, name to url. + */ + let _pluginsPending = {}; + + let _pluginsInstalled = []; + + let _pluginsPendingCount = -1; + + const PRELOADED_PROTOCOL = 'preloaded:'; + + const UNKNOWN_PLUGIN = 'unknown'; + + const PANEL_ENDPOINTS_MAPPING = { + CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK: 'change-view-integration', + CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item', + }; + + const PLUGIN_LOADING_TIMEOUT_MS = 10000; let _restAPI; + const getRestAPI = () => { if (!_restAPI) { _restAPI = document.createElement('gr-rest-api-interface'); @@ -37,6 +51,14 @@ return _restAPI; }; + let _reporting; + const getReporting = () => { + if (!_reporting) { + _reporting = document.createElement('gr-reporting'); + } + return _reporting; + }; + // TODO (viktard): deprecate in favor of GrPluginRestApi. function send(method, url, opt_callback, opt_payload) { return getRestAPI().send(method, url, opt_payload).then(response => { @@ -81,7 +103,33 @@ // http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html window.$wnd = window; + function flushPreinstalls() { + if (window.Gerrit.flushPreinstalls) { + window.Gerrit.flushPreinstalls(); + } + } + + function installPreloadedPlugins() { + if (!Gerrit._preloadedPlugins) { return; } + for (const name in Gerrit._preloadedPlugins) { + if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; } + const callback = Gerrit._preloadedPlugins[name]; + Gerrit.install(callback, API_VERSION, PRELOADED_PROTOCOL + name); + } + } + function getPluginNameFromUrl(url) { + if (!(url instanceof URL)) { + try { + url = new URL(url); + } catch (e) { + console.warn(e); + return null; + } + } + if (url.protocol === PRELOADED_PROTOCOL) { + return url.pathname; + } const base = Gerrit.BaseUrlBehavior.getBaseUrl(); const pathname = url.pathname.replace(base, ''); // Site theme is server from predefined path. @@ -92,7 +140,11 @@ url.href, '— Unable to determine name.'); return; } - return pathname.split('/')[2]; + // Pathname should normally look like this: + // /plugins/PLUGINNAME/static/SCRIPTNAME.html + // Or, for app/samples: + // /plugins/PLUGINNAME.html + return pathname.split('/')[2].split('.')[0]; } function Plugin(opt_url) { @@ -104,13 +156,24 @@ return; } this.deprecated = { + _loadedGwt: deprecatedAPI._loadedGwt.bind(this), install: deprecatedAPI.install.bind(this), - popup: deprecatedAPI.popup.bind(this), onAction: deprecatedAPI.onAction.bind(this), + panel: deprecatedAPI.panel.bind(this), + popup: deprecatedAPI.popup.bind(this), + screen: deprecatedAPI.screen.bind(this), + settingsScreen: deprecatedAPI.settingsScreen.bind(this), }; this._url = new URL(opt_url); this._name = getPluginNameFromUrl(this._url); + if (this._url.protocol === PRELOADED_PROTOCOL) { + // Original plugin URL is used in plugin assets URLs calculation. + const assetsBaseUrl = window.ASSETS_PATH || + (window.location.origin + Gerrit.BaseUrlBehavior.getBaseUrl()); + this._url = new URL(assetsBaseUrl + '/plugins/' + this._name + + '/static/' + this._name + '.js'); + } } Plugin._sharedAPIElement = document.createElement('gr-js-api-interface'); @@ -159,6 +222,13 @@ this._name + (opt_path || '/'); }; + Plugin.prototype.screenUrl = function(opt_screenName) { + const origin = this._url.origin; + const base = Gerrit.BaseUrlBehavior.getBaseUrl(); + const tokenPart = opt_screenName ? '/' + opt_screenName : ''; + return `${origin}${base}/x/${this.getPluginName()}${tokenPart}`; + }; + Plugin.prototype._send = function(method, url, opt_callback, opt_payload) { return send(method, this.url(url), opt_callback, opt_payload); }; @@ -182,6 +252,10 @@ return Gerrit.delete(this.url(url), opt_callback); }; + Plugin.prototype.annotationApi = function() { + return new GrAnnotationActionsInterface(this); + }; + Plugin.prototype.changeActions = function() { return new GrChangeActionsInterface(this, Plugin._sharedAPIElement.getElement( @@ -203,7 +277,19 @@ }; Plugin.prototype.project = function() { - return new GrProjectApi(this); + return new GrRepoApi(this); + }; + + Plugin.prototype.changeMetadata = function() { + return new GrChangeMetadataApi(this); + }; + + Plugin.prototype.admin = function() { + return new GrAdminApi(this); + }; + + Plugin.prototype.settings = function() { + return new GrSettingsApi(this); }; /** @@ -227,13 +313,37 @@ Plugin.prototype.popup = function(moduleName) { if (typeof moduleName !== 'string') { - throw new Error('deprecated, use deprecated.popup'); + console.error('.popup(element) deprecated, use .popup(moduleName)!'); + return; } const api = new GrPopupInterface(this, moduleName); return api.open(); }; + Plugin.prototype.panel = function() { + console.error('.panel() is deprecated! ' + + 'Use registerCustomComponent() instead.'); + }; + + Plugin.prototype.settingsScreen = function() { + console.error('.settingsScreen() is deprecated! ' + + 'Use .settings() instead.'); + }; + + Plugin.prototype.screen = function(screenName, opt_moduleName) { + if (opt_moduleName && typeof opt_moduleName !== 'string') { + console.error('.screen(pattern, callback) deprecated, use ' + + '.screen(screenName, opt_moduleName)!'); + return; + } + return this.registerCustomComponent( + Gerrit._getPluginScreenName(this.getPluginName(), screenName), + opt_moduleName); + }; + const deprecatedAPI = { + _loadedGwt: ()=> {}, + install() { console.log('Installing deprecated APIs is deprecated!'); for (const method in this.deprecated) { @@ -262,32 +372,112 @@ } this.on('showchange', (change, revision) => { const details = this.changeActions().getActionDetails(action); + if (!details) { + console.warn( + `${this.getPluginName()} onAction error: ${action} not found!`); + return; + } this.changeActions().addTapListener(details.__key, () => { callback(new GrPluginActionContext(this, details, change, revision)); }); }); }, + screen(pattern, callback) { + console.warn('plugin.deprecated.screen is deprecated,' + + ' use plugin.screen instead!'); + if (pattern instanceof RegExp) { + console.error('deprecated.screen() does not support RegExp. ' + + 'Please use strings for patterns.'); + return; + } + this.hook(Gerrit._getPluginScreenName(this.getPluginName(), pattern)) + .onAttached(el => { + el.style.display = 'none'; + callback({ + body: el, + token: el.token, + onUnload: () => {}, + setTitle: () => {}, + setWindowTitle: () => {}, + show: () => { + el.style.display = 'initial'; + }, + }); + }); + }, + + settingsScreen(path, menu, callback) { + console.warn('.settingsScreen() is deprecated! Use .settings() instead.'); + const hook = this.settings() + .title(menu) + .token(path) + .module('div') + .build(); + hook.onAttached(el => { + el.style.display = 'none'; + const body = el.querySelector('div'); + callback({ + body, + onUnload: () => {}, + setTitle: () => {}, + setWindowTitle: () => {}, + show: () => { + el.style.display = 'initial'; + }, + }); + }); + }, + + panel(extensionpoint, callback) { + console.warn('.panel() is deprecated! ' + + 'Use registerCustomComponent() instead.'); + const endpoint = PANEL_ENDPOINTS_MAPPING[extensionpoint]; + if (!endpoint) { + console.warn(`.panel ${extensionpoint} not supported!`); + return; + } + this.hook(endpoint).onAttached(el => callback({ + body: el, + p: { + CHANGE_INFO: el.change, + REVISION_INFO: el.revision, + }, + onUnload: () => {}, + })); + }, }; + flushPreinstalls(); + const Gerrit = window.Gerrit || {}; + let _resolveAllPluginsLoaded = null; + let _allPluginsPromise = null; + + Gerrit._endpoints = new GrPluginEndpoints(); + // Provide reset plugins function to clear installed plugins between tests. const app = document.querySelector('#app'); if (!app) { // No gr-app found (running tests) + Gerrit._installPreloadedPlugins = installPreloadedPlugins; + Gerrit._flushPreinstalls = flushPreinstalls; Gerrit._resetPlugins = () => { - for (const k of Object.keys(plugins)) { - delete plugins[k]; + _allPluginsPromise = null; + _pluginsInstalled = []; + _pluginsPending = {}; + _pluginsPendingCount = -1; + _reporting = null; + _resolveAllPluginsLoaded = null; + _restAPI = null; + Gerrit._endpoints = new GrPluginEndpoints(); + for (const k of Object.keys(_plugins)) { + delete _plugins[k]; } }; } - // Number of plugins to initialize, -1 means 'not yet known'. - Gerrit._pluginsPending = -1; - - Gerrit._endpoints = new GrPluginEndpoints(); - Gerrit.getPluginName = function() { console.warn('Gerrit.getPluginName is not supported in PolyGerrit.', 'Please use plugin.getPluginName() instead.'); @@ -307,32 +497,31 @@ }; Gerrit.install = function(callback, opt_version, opt_src) { + // HTML import polyfill adds __importElement pointing to the import tag. + const script = document.currentScript && + (document.currentScript.__importElement || document.currentScript); + const src = opt_src || (script && (script.src || script.baseURI)); + const name = getPluginNameFromUrl(src); + if (opt_version && opt_version !== API_VERSION) { - console.warn('Only version ' + API_VERSION + - ' is supported in PolyGerrit. ' + opt_version + ' was given.'); - Gerrit._pluginInstalled(); + Gerrit._pluginInstallError(`Plugin ${name} install error: only version ` + + API_VERSION + ' is supported in PolyGerrit. ' + opt_version + + ' was given.'); return; } - const src = opt_src || (document.currentScript && - (document.currentScript.src || document.currentScript.baseURI)); - const name = getPluginNameFromUrl(new URL(src)); - const existingPlugin = plugins[name]; + const existingPlugin = _plugins[name]; const plugin = existingPlugin || new Plugin(src); try { callback(plugin); - plugins[name] = plugin; + if (name) { + _plugins[name] = plugin; + } + if (!existingPlugin) { + Gerrit._pluginInstalled(src); + } } catch (e) { - console.warn(`${name} install failed: ${e.name}: ${e.message}`); - } - // Don't double count plugins that may have an html and js install. - // TODO(beckysiegel) remove name check once name issue is resolved. - // If there isn't a name, it's due to an issue with the polyfill for - // html imports in Safari/Firefox. In this case, other plugin related - // features may still be broken, but still make sure to call. - // _pluginInstalled. - if (!name || !existingPlugin) { - Gerrit._pluginInstalled(); + Gerrit._pluginInstallError(`${e.name}: ${e.message}`); } }; @@ -377,48 +566,105 @@ }; /** - * Polyfill GWT API dependencies to avoid runtime exceptions when loading - * GWT-compiled plugins. - * @deprecated Not supported in PolyGerrit. + * Install "stepping stones" API for GWT-compiled plugins by default. + * @deprecated best effort support, will be removed with GWT UI. */ - Gerrit.installGwt = function() { - Gerrit._pluginInstalled(); - return GWT_PLUGIN_STUB; + Gerrit.installGwt = function(url) { + const name = getPluginNameFromUrl(url); + let plugin; + try { + plugin = _plugins[name] || new Plugin(url); + plugin.deprecated.install(); + Gerrit._pluginInstalled(url); + } catch (e) { + Gerrit._pluginInstallError(`${e.name}: ${e.message}`); + } + return plugin; }; - Gerrit._allPluginsPromise = null; - Gerrit._resolveAllPluginsLoaded = null; - Gerrit.awaitPluginsLoaded = function() { - if (!Gerrit._allPluginsPromise) { + if (!_allPluginsPromise) { if (Gerrit._arePluginsLoaded()) { - Gerrit._allPluginsPromise = Promise.resolve(); + _allPluginsPromise = Promise.resolve(); } else { - Gerrit._allPluginsPromise = new Promise(resolve => { - Gerrit._resolveAllPluginsLoaded = resolve; - }); + let timeoutId; + _allPluginsPromise = + Promise.race([ + new Promise(resolve => _resolveAllPluginsLoaded = resolve), + new Promise(resolve => timeoutId = setTimeout( + Gerrit._pluginLoadingTimeout, PLUGIN_LOADING_TIMEOUT_MS)), + ]).then(() => clearTimeout(timeoutId)); } } - return Gerrit._allPluginsPromise; + return _allPluginsPromise; + }; + + Gerrit._pluginLoadingTimeout = function() { + console.error(`Failed to load plugins: ${Object.keys(_pluginsPending)}`); + Gerrit._setPluginsPending([]); + }; + + Gerrit._setPluginsPending = function(plugins) { + _pluginsPending = plugins.reduce((o, url) => { + // TODO(viktard): Remove guard (@see Issue 8962) + o[getPluginNameFromUrl(url) || UNKNOWN_PLUGIN] = url; + return o; + }, {}); + Gerrit._setPluginsCount(Object.keys(_pluginsPending).length); }; Gerrit._setPluginsCount = function(count) { - Gerrit._pluginsPending = count; + _pluginsPendingCount = count; if (Gerrit._arePluginsLoaded()) { - document.createElement('gr-reporting').pluginsLoaded(); - if (Gerrit._resolveAllPluginsLoaded) { - Gerrit._resolveAllPluginsLoaded(); + getReporting().pluginsLoaded(_pluginsInstalled); + if (_resolveAllPluginsLoaded) { + _resolveAllPluginsLoaded(); } } }; - Gerrit._pluginInstalled = function() { - Gerrit._setPluginsCount(Gerrit._pluginsPending - 1); + Gerrit._pluginInstallError = function(message) { + document.dispatchEvent(new CustomEvent('show-alert', { + detail: { + message: `Plugin install error: ${message}`, + }, + })); + console.info(`Plugin install error: ${message}`); + Gerrit._setPluginsCount(_pluginsPendingCount - 1); + }; + + Gerrit._pluginInstalled = function(url) { + const name = getPluginNameFromUrl(url) || UNKNOWN_PLUGIN; + if (!_pluginsPending[name]) { + console.warn(`Unexpected plugin ${name} installed from ${url}.`); + } else { + delete _pluginsPending[name]; + _pluginsInstalled.push(name); + Gerrit._setPluginsCount(_pluginsPendingCount - 1); + console.log(`Plugin ${name} installed.`); + } }; Gerrit._arePluginsLoaded = function() { - return Gerrit._pluginsPending === 0; + return _pluginsPendingCount === 0; + }; + + Gerrit._getPluginScreenName = function(pluginName, screenName) { + return `${pluginName}-screen-${screenName}`; + }; + + Gerrit._isPluginPreloaded = function(url) { + const name = getPluginNameFromUrl(url); + if (name && Gerrit._preloadedPlugins) { + return name in Gerrit._preloadedPlugins; + } else { + return false; + } }; window.Gerrit = Gerrit; + + // Preloaded plugins should be installed after Gerrit.install() is set, + // since plugin preloader substitutes Gerrit.install() temporarily. + installPreloadedPlugins(); })(window); |