Merge "Move plugin loading related methods to PluginLoader." into stable-3.1

This commit is contained in:
David Pursehouse
2019-11-09 09:37:57 +00:00
committed by Gerrit Code Review
25 changed files with 1033 additions and 519 deletions

View File

@@ -89,6 +89,7 @@ limitations under the License.
teardown(() => {
sandbox.restore();
Gerrit._testOnly_resetPlugins();
});
suite('by default', () => {
@@ -141,7 +142,7 @@ limitations under the License.
new URL('test/plugin.html?' + Math.random(),
window.location.href).toString());
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
element = createElement();
});

View File

@@ -725,7 +725,7 @@ limitations under the License.
},
'0.1',
'http://some/plugins/url.html');
Gerrit._setPluginsCount(0);
Gerrit._loadPlugins([]);
flush(() => {
assert.strictEqual(hookEl.plugin, plugin);
assert.strictEqual(hookEl.change, element.change);

View File

@@ -81,7 +81,7 @@ limitations under the License.
});
element = fixture('basic');
sandbox.stub(element.$.actions, 'reload').returns(Promise.resolve());
Gerrit._setPluginsCount(0);
Gerrit._loadPlugins([]);
});
teardown(done => {

View File

@@ -426,7 +426,8 @@
},
_computePluginScreenName({plugin, screen}) {
return Gerrit._getPluginScreenName(plugin, screen);
if (!plugin || !screen) return '';
return `${plugin}-screen-${screen}`;
},
_logWelcome() {

View File

@@ -39,7 +39,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
Gerrit._loadPlugins([]);
adminApi = plugin.admin();
});

View File

@@ -67,7 +67,7 @@ limitations under the License.
replacementHook = plugin.registerCustomComponent(
'second', 'other-module', {replace: true});
// Mimic all plugins loaded.
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
flush(done);
});

View File

@@ -27,33 +27,29 @@
},
},
behaviors: [
Gerrit.BaseUrlBehavior,
],
_configChanged(config) {
const plugins = config.plugin;
const htmlPlugins = (plugins.html_resource_paths || [])
.map(p => this._urlFor(p))
.filter(p => !Gerrit._isPluginPreloaded(p));
const htmlPlugins = (plugins.html_resource_paths || []);
const jsPlugins =
this._handleMigrations(plugins.js_resource_paths || [], htmlPlugins)
.map(p => this._urlFor(p))
.filter(p => !Gerrit._isPluginPreloaded(p));
this._handleMigrations(plugins.js_resource_paths || [], htmlPlugins);
const shouldLoadTheme = config.default_theme &&
!Gerrit._isPluginPreloaded('preloaded:gerrit-theme');
const defaultTheme =
shouldLoadTheme ? this._urlFor(config.default_theme) : null;
const themeToLoad =
shouldLoadTheme ? [config.default_theme] : [];
// Theme should be loaded first if has one to have better UX
const pluginsPending =
[].concat(jsPlugins, htmlPlugins, defaultTheme || []);
Gerrit._setPluginsPending(pluginsPending);
if (defaultTheme) {
// Make theme first to be first to load.
// Load sync to work around rare theme loading race condition.
this._importHtmlPlugins([defaultTheme], true);
themeToLoad.concat(jsPlugins, htmlPlugins);
const pluginOpts = {};
if (shouldLoadTheme) {
// Theme needs to be loaded synchronous.
pluginOpts[config.default_theme] = {sync: true};
}
this._loadJsPlugins(jsPlugins);
this._importHtmlPlugins(htmlPlugins);
Gerrit._loadPlugins(pluginsPending, pluginOpts);
},
/**
@@ -66,53 +62,5 @@
return !htmlPlugins.includes(counterpart);
});
},
/**
* @suppress {checkTypes}
* States that it expects no more than 3 parameters, but that's not true.
* @todo (beckysiegel) check Polymer annotations and submit change.
* @param {Array} plugins
* @param {boolean=} opt_sync
*/
_importHtmlPlugins(plugins, opt_sync) {
const async = !opt_sync;
for (const url of plugins) {
// onload (second param) needs to be a function. When null or undefined
// were passed, plugins were not loaded correctly.
(this.importHref || Polymer.importHref)(
this._urlFor(url), () => {},
Gerrit._pluginInstallError.bind(null, `${url} import error`),
async);
}
},
_loadJsPlugins(plugins) {
for (const url of plugins) {
this._createScriptTag(this._urlFor(url));
}
},
_createScriptTag(url) {
const el = document.createElement('script');
el.defer = true;
el.src = url;
el.onerror = Gerrit._pluginInstallError.bind(null, `${url} load error`);
return document.body.appendChild(el);
},
_urlFor(pathOrUrl) {
if (!pathOrUrl) {
return pathOrUrl;
}
if (pathOrUrl.startsWith('preloaded:') ||
pathOrUrl.startsWith('http')) {
// Plugins are loaded from another domain or preloaded.
return pathOrUrl;
}
if (!pathOrUrl.startsWith('/')) {
pathOrUrl = '/' + pathOrUrl;
}
return window.location.origin + this.getBaseUrl() + pathOrUrl;
},
});
})();

View File

@@ -38,195 +38,57 @@ limitations under the License.
suite('gr-plugin-host tests', () => {
let element;
let sandbox;
let url;
setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
sandbox.stub(document.body, 'appendChild');
sandbox.stub(element, 'importHref');
url = window.location.origin;
});
teardown(() => {
sandbox.restore();
});
test('counts plugins', () => {
sandbox.stub(Gerrit, '_setPluginsCount');
test('load plugins should be called', () => {
sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
plugin: {
html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
js_resource_paths: ['plugins/42'],
},
};
assert.isTrue(Gerrit._setPluginsCount.calledWith(3));
assert.isTrue(Gerrit._loadPlugins.calledOnce);
assert.isTrue(Gerrit._loadPlugins.calledWith([
'plugins/42', 'plugins/foo/bar', 'plugins/baz',
], {}));
});
test('imports relative html plugins from config', () => {
sandbox.stub(Gerrit, '_pluginInstallError');
element.config = {
plugin: {html_resource_paths: ['foo/bar', 'baz']},
};
assert.equal(element.importHref.firstCall.args[0], url + '/foo/bar');
assert.isTrue(element.importHref.firstCall.args[3]);
assert.equal(element.importHref.secondCall.args[0], url + '/baz');
assert.isTrue(element.importHref.secondCall.args[3]);
assert.equal(Gerrit._pluginInstallError.callCount, 0);
element.importHref.firstCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 1);
element.importHref.secondCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 2);
});
test('imports relative html plugins from config with a base url', () => {
sandbox.stub(Gerrit, '_pluginInstallError');
sandbox.stub(element, 'getBaseUrl').returns('/the-base');
element.config = {
plugin: {html_resource_paths: ['foo/bar', 'baz']}};
assert.equal(element.importHref.firstCall.args[0],
url + '/the-base/foo/bar');
assert.isTrue(element.importHref.firstCall.args[3]);
assert.equal(element.importHref.secondCall.args[0],
url + '/the-base/baz');
assert.isTrue(element.importHref.secondCall.args[3]);
assert.equal(Gerrit._pluginInstallError.callCount, 0);
element.importHref.firstCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 1);
element.importHref.secondCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 2);
});
test('importHref is not called with null callback functions', () => {
const plugins = ['path/to/plugin'];
element._importHtmlPlugins(plugins);
assert.isTrue(element.importHref.calledOnce);
assert.isFunction(element.importHref.lastCall.args[1]);
assert.isFunction(element.importHref.lastCall.args[2]);
});
test('imports absolute html plugins from config', () => {
sandbox.stub(Gerrit, '_pluginInstallError');
test('theme plugins should be loaded if enabled', () => {
sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
default_theme: 'gerrit-theme.html',
plugin: {
html_resource_paths: [
'http://example.com/foo/bar',
'https://example.com/baz',
],
html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
js_resource_paths: ['plugins/42'],
},
};
assert.equal(element.importHref.firstCall.args[0],
'http://example.com/foo/bar');
assert.isTrue(element.importHref.firstCall.args[3]);
assert.equal(element.importHref.secondCall.args[0],
'https://example.com/baz');
assert.isTrue(element.importHref.secondCall.args[3]);
assert.equal(Gerrit._pluginInstallError.callCount, 0);
element.importHref.firstCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 1);
element.importHref.secondCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 2);
assert.isTrue(Gerrit._loadPlugins.calledOnce);
assert.isTrue(Gerrit._loadPlugins.calledWith([
'gerrit-theme.html', 'plugins/42', 'plugins/foo/bar', 'plugins/baz',
], {'gerrit-theme.html': {sync: true}}));
});
test('adds js plugins from config to the body', () => {
element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
assert.isTrue(document.body.appendChild.calledTwice);
});
test('imports relative js plugins from config', () => {
sandbox.stub(element, '_createScriptTag');
element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
assert.isTrue(element._createScriptTag.calledWith(url + '/foo/bar'));
assert.isTrue(element._createScriptTag.calledWith(url + '/baz'));
});
test('imports relative html plugins from config with a base url', () => {
sandbox.stub(element, '_createScriptTag');
sandbox.stub(element, 'getBaseUrl').returns('/the-base');
element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
assert.isTrue(element._createScriptTag.calledWith(
url + '/the-base/foo/bar'));
assert.isTrue(element._createScriptTag.calledWith(
url + '/the-base/baz'));
});
test('imports absolute html plugins from config', () => {
sandbox.stub(element, '_createScriptTag');
element.config = {
plugin: {
js_resource_paths: [
'http://example.com/foo/bar',
'https://example.com/baz',
],
},
};
assert.isTrue(element._createScriptTag.calledWith(
'http://example.com/foo/bar'));
assert.isTrue(element._createScriptTag.calledWith(
'https://example.com/baz'));
});
test('default theme is loaded with html plugins', () => {
sandbox.stub(Gerrit, '_pluginInstallError');
element.config = {
default_theme: '/oof',
plugin: {
html_resource_paths: ['some'],
},
};
assert.equal(element.importHref.firstCall.args[0], url + '/oof');
assert.isFalse(element.importHref.firstCall.args[3]);
assert.equal(element.importHref.secondCall.args[0], url + '/some');
assert.isTrue(element.importHref.secondCall.args[3]);
assert.equal(Gerrit._pluginInstallError.callCount, 0);
element.importHref.firstCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 1);
element.importHref.secondCall.args[2]();
assert.equal(Gerrit._pluginInstallError.callCount, 2);
});
test('default theme is loaded with html plugins', () => {
sandbox.stub(Gerrit, '_setPluginsPending');
element.config = {
default_theme: '/oof',
plugin: {},
};
assert.isTrue(Gerrit._setPluginsPending.calledWith([url + '/oof']));
});
test('skips default theme loading if preloaded', () => {
test('skip theme if preloaded', () => {
sandbox.stub(Gerrit, '_isPluginPreloaded')
.withArgs('preloaded:gerrit-theme').returns(true);
sandbox.stub(Gerrit, '_setPluginsPending');
sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
default_theme: '/oof',
plugin: {},
};
assert.isFalse(element.importHref.calledWith(url + '/oof'));
});
test('skips preloaded plugins', () => {
sandbox.stub(Gerrit, '_isPluginPreloaded')
.withArgs(url + '/plugins/foo/bar').returns(true)
.withArgs(url + '/plugins/42').returns(true);
sandbox.stub(Gerrit, '_setPluginsCount');
sandbox.stub(Gerrit, '_setPluginsPending');
sandbox.stub(element, '_createScriptTag');
element.config = {
plugin: {
html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
js_resource_paths: ['plugins/42'],
},
};
assert.isTrue(
Gerrit._setPluginsPending.calledWith([url + '/plugins/baz']));
assert.equal(element._createScriptTag.callCount, 0);
assert.isTrue(element.importHref.calledWith(url + '/plugins/baz'));
assert.isTrue(Gerrit._loadPlugins.calledOnce);
assert.isTrue(Gerrit._loadPlugins.calledWith([], {}));
});
});
</script>

View File

@@ -46,7 +46,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
Gerrit._loadPlugins([]);
repoApi = plugin.project();
});

View File

@@ -48,7 +48,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
Gerrit._loadPlugins([]);
settingsApi = plugin.settings();
});

View File

@@ -46,7 +46,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
Gerrit._loadPlugins([]);
stylesApi = plugin.styles();
});
@@ -76,7 +76,7 @@ limitations under the License.
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
Gerrit._loadPlugins([]);
stylesApi = plugin.styles();
displayInlineStyle = stylesApi.css('display: inline');
displayNoneStyle = stylesApi.css('display: none');

View File

@@ -67,7 +67,7 @@ limitations under the License.
stub('gr-custom-plugin-header', {
ready() { customHeader = this; },
});
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
});
test('sets logo and title', done => {

View File

@@ -117,7 +117,7 @@ limitations under the License.
assert.strictEqual(element.style.backgroundImage, '');
// Emulate plugins loaded.
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
Promise.all([
element.$.restAPI.getConfig(),
@@ -155,7 +155,7 @@ limitations under the License.
assert.isFalse(element.hasAttribute('hidden'));
// Emulate plugins loaded.
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
return Promise.all([
element.$.restAPI.getConfig(),
@@ -197,7 +197,7 @@ limitations under the License.
_account_id: 123,
};
// Emulate plugins loaded.
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
return Promise.all([
element.$.restAPI.getConfig(),

View File

@@ -19,6 +19,7 @@
'use strict';
const PRELOADED_PROTOCOL = 'preloaded:';
const PLUGIN_LOADING_TIMEOUT_MS = 10000;
let _restAPI;
function getRestAPI() {
@@ -28,6 +29,10 @@
return _restAPI;
}
function getBaseUrl() {
return Gerrit.BaseUrlBehavior.getBaseUrl();
}
/**
* Retrieves the name of the plugin base on the url.
* @param {string|URL} url
@@ -96,6 +101,9 @@
getPluginNameFromUrl,
send,
getRestAPI,
getBaseUrl,
PRELOADED_PROTOCOL,
PLUGIN_LOADING_TIMEOUT_MS,
// TEST only methods
testOnly_resetInternalState,

View File

@@ -58,13 +58,14 @@ breaking changes to gr-change-actions wont be noticed.
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
// Mimic all plugins loaded.
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
changeActions = plugin.changeActions();
element = fixture('basic');
});
teardown(() => {
changeActions = null;
Gerrit._testOnly_resetPlugins();
});
test('does not throw', ()=> {
@@ -85,11 +86,12 @@ breaking changes to gr-change-actions wont be noticed.
'http://test.com/plugins/testplugin/static/test.js');
changeActions = plugin.changeActions();
// Mimic all plugins loaded.
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
});
teardown(() => {
changeActions = null;
Gerrit._testOnly_resetPlugins();
});
test('property existence', () => {

View File

@@ -23,42 +23,12 @@
(function(window) {
'use strict';
/**
* Hash of loaded and installed plugins, name to Plugin object.
*/
const _plugins = {};
/**
* Array of plugin URLs to be loaded, name to url.
*/
let _pluginsPending = {};
let _pluginsInstalled = [];
let _pluginsPendingCount = -1;
const UNKNOWN_PLUGIN = 'unknown';
const PRELOADED_PROTOCOL = 'preloaded:';
const PLUGIN_LOADING_TIMEOUT_MS = 10000;
let _reporting;
const getReporting = () => {
if (!_reporting) {
_reporting = document.createElement('gr-reporting');
}
return _reporting;
};
// Import utils methods
const {
getPluginNameFromUrl,
send,
getRestAPI,
} = window._apiUtils;
const API_VERSION = '0.1';
/**
* Trigger the preinstalls for bundled plugins.
* This needs to happen before Gerrit as plugin bundle overrides the Gerrit.
@@ -72,9 +42,7 @@
window.Gerrit = window.Gerrit || {};
const Gerrit = window.Gerrit;
let _resolveAllPluginsLoaded = null;
let _allPluginsPromise = null;
Gerrit._pluginLoader = new PluginLoader();
Gerrit._endpoints = new GrPluginEndpoints();
@@ -85,20 +53,13 @@
const {
testOnly_resetInternalState,
} = window._apiUtils;
Gerrit._testOnly_installPreloadedPlugins = installPreloadedPlugins;
Gerrit._testOnly_installPreloadedPlugins = (...args) => Gerrit._pluginLoader
.installPreloadedPlugins(...args);
Gerrit._testOnly_flushPreinstalls = flushPreinstalls;
Gerrit._testOnly_resetPlugins = () => {
_allPluginsPromise = null;
_pluginsInstalled = [];
_pluginsPending = {};
_pluginsPendingCount = -1;
_reporting = null;
_resolveAllPluginsLoaded = null;
testOnly_resetInternalState();
Gerrit._endpoints = new GrPluginEndpoints();
for (const k of Object.keys(_plugins)) {
delete _plugins[k];
}
Gerrit._pluginLoader = new PluginLoader();
};
}
@@ -122,36 +83,7 @@
};
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);
let src = opt_src || (script && script.src);
if (!src || src.startsWith('data:')) {
src = script && script.baseURI;
}
const name = getPluginNameFromUrl(src);
if (opt_version && opt_version !== API_VERSION) {
Gerrit._pluginInstallError(`Plugin ${name} install error: only version ` +
API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
' was given.');
return;
}
const existingPlugin = _plugins[name];
const plugin = existingPlugin || new Plugin(src);
try {
callback(plugin);
if (name) {
_plugins[name] = plugin;
}
if (!existingPlugin) {
Gerrit._pluginInstalled(src);
}
} catch (e) {
Gerrit._pluginInstallError(`${e.name}: ${e.message}`);
}
Gerrit._pluginLoader.install(callback, opt_version, opt_src);
};
Gerrit.getLoggedIn = function() {
@@ -195,96 +127,33 @@
};
Gerrit.awaitPluginsLoaded = function() {
if (!_allPluginsPromise) {
if (Gerrit._arePluginsLoaded()) {
_allPluginsPromise = Promise.resolve();
} else {
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 _allPluginsPromise;
return Gerrit._pluginLoader.awaitPluginsLoaded();
};
Gerrit._pluginLoadingTimeout = function() {
console.error(`Failed to load plugins: ${Object.keys(_pluginsPending)}`);
Gerrit._setPluginsPending([]);
};
// TODO(taoalpha): consider removing these proxy methods
// and using _pluginLoader directly
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) {
_pluginsPendingCount = count;
if (Gerrit._arePluginsLoaded()) {
getReporting().pluginsLoaded(_pluginsInstalled);
if (_resolveAllPluginsLoaded) {
_resolveAllPluginsLoaded();
}
}
};
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);
getReporting().pluginLoaded(name);
console.log(`Plugin ${name} installed.`);
}
Gerrit._loadPlugins = function(plugins, opt_option) {
Gerrit._pluginLoader.loadPlugins(plugins, opt_option);
};
Gerrit._arePluginsLoaded = function() {
return _pluginsPendingCount === 0;
};
Gerrit._getPluginScreenName = function(pluginName, screenName) {
return `${pluginName}-screen-${screenName}`;
return Gerrit._pluginLoader.arePluginsLoaded;
};
Gerrit._isPluginPreloaded = function(url) {
const name = getPluginNameFromUrl(url);
if (name && Gerrit._preloadedPlugins) {
return name in Gerrit._preloadedPlugins;
} else {
return false;
}
return Gerrit._pluginLoader.isPluginPreloaded(url);
};
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);
}
}
Gerrit._isPluginEnabled = function(pathOrUrl) {
return Gerrit._pluginLoader.isPluginEnabled(pathOrUrl);
};
Gerrit._isPluginLoaded = function(pathOrUrl) {
return Gerrit._pluginLoader.isPluginLoaded(pathOrUrl);
};
// Preloaded plugins should be installed after Gerrit.install() is set,
// since plugin preloader substitutes Gerrit.install() temporarily.
installPreloadedPlugins();
Gerrit._pluginLoader.installPreloadedPlugins();
})(window);

View File

@@ -37,11 +37,11 @@ limitations under the License.
<script>
suite('gr-gerrit tests', () => {
let element;
let plugin;
let sandbox;
let sendStub;
setup(() => {
this.clock = sinon.useFakeTimers();
sandbox = sinon.sandbox.create();
sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
stub('gr-rest-api-interface', {
@@ -53,136 +53,48 @@ limitations under the License.
},
});
element = fixture('basic');
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
Gerrit._setPluginsPending([]);
});
teardown(() => {
this.clock.restore();
sandbox.restore();
element._removeEventCallbacks();
plugin = null;
Gerrit._testOnly_resetPlugins();
});
test('reuse plugin for install calls', () => {
let otherPlugin;
Gerrit.install(p => { otherPlugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
assert.strictEqual(plugin, otherPlugin);
});
test('flushes preinstalls if provided', () => {
assert.doesNotThrow(() => {
Gerrit._testOnly_flushPreinstalls();
suite('proxy methods', () => {
test('Gerrit._isPluginEnabled proxy to pluginLoader', () => {
const stubFn = sandbox.stub();
sandbox.stub(
Gerrit._pluginLoader,
'isPluginEnabled',
(...args) => stubFn(...args)
);
Gerrit._isPluginEnabled('test_plugin');
assert.isTrue(stubFn.calledWith('test_plugin'));
});
window.Gerrit.flushPreinstalls = sandbox.stub();
Gerrit._testOnly_flushPreinstalls();
assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
delete window.Gerrit.flushPreinstalls;
});
test('versioning', () => {
const callback = sandbox.spy();
Gerrit.install(callback, '0.0pre-alpha');
assert(callback.notCalled);
});
test('_setPluginsCount', done => {
stub('gr-reporting', {
pluginsLoaded() {
done();
},
test('Gerrit._isPluginLoaded proxy to pluginLoader', () => {
const stubFn = sandbox.stub();
sandbox.stub(
Gerrit._pluginLoader,
'isPluginLoaded',
(...args) => stubFn(...args)
);
Gerrit._isPluginLoaded('test_plugin');
assert.isTrue(stubFn.calledWith('test_plugin'));
});
Gerrit._setPluginsCount(0);
});
test('_arePluginsLoaded', () => {
assert.isTrue(Gerrit._arePluginsLoaded());
Gerrit._setPluginsCount(1);
assert.isFalse(Gerrit._arePluginsLoaded());
Gerrit._setPluginsCount(0);
assert.isTrue(Gerrit._arePluginsLoaded());
});
test('_pluginInstalled', () => {
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
test('Gerrit._isPluginPreloaded proxy to pluginLoader', () => {
const stubFn = sandbox.stub();
sandbox.stub(
Gerrit._pluginLoader,
'isPluginPreloaded',
(...args) => stubFn(...args)
);
Gerrit._isPluginPreloaded('test_plugin');
assert.isTrue(stubFn.calledWith('test_plugin'));
});
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
];
Gerrit._setPluginsPending(plugins);
Gerrit._pluginInstalled(plugins[0]);
Gerrit._pluginInstalled(plugins[1]);
assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
});
test('install calls _pluginInstalled', () => {
sandbox.stub(Gerrit, '_pluginInstalled');
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
// testplugin has already been installed once (in setup).
assert.isFalse(Gerrit._pluginInstalled.called);
// testplugin2 plugin has not yet been installed.
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin2/static/test.js');
assert.isTrue(Gerrit._pluginInstalled.calledOnce);
});
test('plugin install errors mark plugins as loaded', () => {
Gerrit._setPluginsCount(1);
Gerrit.install(() => {}, '0.0pre-alpha');
return Gerrit.awaitPluginsLoaded();
});
test('multiple ui plugins per java plugin', () => {
const file1 = 'http://test.com/plugins/qaz/static/foo.nocache.js';
const file2 = 'http://test.com/plugins/qaz/static/bar.js';
Gerrit._setPluginsPending([file1, file2]);
Gerrit.install(() => {}, '0.1', file1);
Gerrit.install(() => {}, '0.1', file2);
return Gerrit.awaitPluginsLoaded();
});
test('plugin install errors shows toasts', () => {
const alertStub = sandbox.stub();
document.addEventListener('show-alert', alertStub);
Gerrit._setPluginsCount(1);
Gerrit.install(() => {}, '0.0pre-alpha');
return Gerrit.awaitPluginsLoaded().then(() => {
assert.isTrue(alertStub.calledOnce);
});
});
test('Gerrit._isPluginPreloaded', () => {
Gerrit._preloadedPlugins = {baz: ()=>{}};
assert.isFalse(Gerrit._isPluginPreloaded('plugins/foo/bar'));
assert.isFalse(Gerrit._isPluginPreloaded('http://a.com/42'));
assert.isTrue(Gerrit._isPluginPreloaded('preloaded:baz'));
Gerrit._preloadedPlugins = null;
});
test('preloaded plugins are installed', () => {
const installStub = sandbox.stub();
Gerrit._preloadedPlugins = {foo: installStub};
Gerrit._testOnly_installPreloadedPlugins();
assert.isTrue(installStub.called);
const pluginApi = installStub.lastCall.args[0];
assert.strictEqual(pluginApi.getPluginName(), 'foo');
});
test('installing preloaded plugin', () => {
let plugin;
window.ASSETS_PATH = 'http://blips.com/chitz';
Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
assert.strictEqual(plugin.getPluginName(), 'foo');
assert.strictEqual(plugin.url('/some/thing.html'),
'http://blips.com/chitz/plugins/foo/some/thing.html');
delete window.ASSETS_PATH;
});
});
</script>

View File

@@ -31,6 +31,13 @@ limitations under the License.
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-js-api-interface">
<!--
Note: the order matters as files depend on each other.
1. gr-api-utils will be used in multiple files below.
2. gr-gerrit depends on gr-plugin-loader, gr-public-js-api and
also gr-plugin-endpoints
3. gr-public-js-api depends on gr-plugin-rest-api
-->
<script src="gr-api-utils.js"></script>
<script src="gr-annotation-actions-context.js"></script>
<script src="gr-annotation-actions-js-api.js"></script>
@@ -41,5 +48,6 @@ limitations under the License.
<script src="gr-plugin-action-context.js"></script>
<script src="gr-plugin-rest-api.js"></script>
<script src="gr-public-js-api.js"></script>
<script src="gr-plugin-loader.js"></script>
<script src="gr-gerrit.js"></script>
</dom-module>

View File

@@ -35,6 +35,7 @@ limitations under the License.
</test-fixture>
<script>
const {PLUGIN_LOADING_TIMEOUT_MS} = window._apiUtils;
suite('gr-js-api-interface tests', () => {
let element;
let plugin;
@@ -48,6 +49,7 @@ limitations under the License.
};
setup(() => {
this.clock = sinon.useFakeTimers();
sandbox = sinon.sandbox.create();
getResponseObjectStub = sandbox.stub().returns(Promise.resolve());
sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
@@ -64,10 +66,11 @@ limitations under the License.
errorStub = sandbox.stub(console, 'error');
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
Gerrit._setPluginsPending([]);
Gerrit._loadPlugins([]);
});
teardown(() => {
this.clock.restore();
sandbox.restore();
element._removeEventCallbacks();
plugin = null;
@@ -194,12 +197,15 @@ limitations under the License.
revisions: {def: {_number: 2}, abc: {_number: 1}},
};
const spy = sandbox.spy();
Gerrit._setPluginsCount(1);
Gerrit._loadPlugins(['plugins/test.html']);
plugin.on(element.EventType.SHOW_CHANGE, spy);
element.handleEvent(element.EventType.SHOW_CHANGE,
{change: testChange, patchNum: 1});
assert.isFalse(spy.called);
Gerrit._setPluginsCount(0);
// Timeout on loading plugins
this.clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
flush(() => {
assert.isTrue(spy.called);
done();
@@ -334,7 +340,6 @@ limitations under the License.
setup(() => {
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/r');
Gerrit._setPluginsCount(1);
Gerrit.install(p => { baseUrlPlugin = p; }, '0.1',
'http://test.com/r/plugins/baseurlplugin/static/test.js');
});

View File

@@ -42,7 +42,6 @@ limitations under the License.
setup(() => {
sandbox = sinon.sandbox.create();
Gerrit._setPluginsCount(1);
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
instance = new GrPluginActionContext(plugin);

View File

@@ -0,0 +1,393 @@
/**
* @license
* Copyright (C) 2019 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';
// Import utils methods
const {
PLUGIN_LOADING_TIMEOUT_MS,
PRELOADED_PROTOCOL,
getPluginNameFromUrl,
getBaseUrl,
} = window._apiUtils;
/**
* @enum {string}
*/
const PluginState = {
/**
* State that indicates the plugin is pending to be loaded.
*/
PENDING: 'PENDING',
/**
* State that indicates the plugin is already loaded.
*/
LOADED: 'LOADED',
/**
* State that indicates the plugin is already loaded.
*/
PRE_LOADED: 'PRE_LOADED',
/**
* State that indicates the plugin failed to load.
*/
LOAD_FAILED: 'LOAD_FAILED',
};
// Prefix for any unrecognized plugin urls.
// Url should match following patterns:
// /plugins/PLUGINNAME/static/SCRIPTNAME.(html|js)
// /plugins/PLUGINNAME.(js|html)
const UNKNOWN_PLUGIN_PREFIX = '__$$__';
// Current API version for Plugin,
// plugins with incompatible version will not be laoded.
const API_VERSION = '0.1';
/**
* PluginLoader, responsible for:
*
* Loading all plugins and handling errors etc.
* Recording plugin state.
* Reporting on plugin loading status.
* Retrieve plugin.
* Check plugin status and if all plugins loaded.
*/
class PluginLoader {
constructor() {
this._pluginListLoaded = false;
/** @type {Map<string,PluginLoader.PluginObject>} */
this._plugins = new Map();
this._reporting = null;
// Promise that resolves when all plugins loaded
this._loadingPromise = null;
// Resolver to resolve _loadingPromise once all plugins loaded
this._loadingResolver = null;
}
_getReporting() {
if (!this._reporting) {
this._reporting = document.createElement('gr-reporting');
}
return this._reporting;
}
/**
* Use the plugin name or use the full url if not recognized.
* @see gr-api-utils#getPluginNameFromUrl
* @param {string|URL} url
*/
_getPluginKeyFromUrl(url) {
return getPluginNameFromUrl(url) ||
`${UNKNOWN_PLUGIN_PREFIX}${url}`;
}
/**
* Load multiple plugins with certain options.
*
* @param {Array<string>} plugins
* @param {Object<string, PluginLoader.PluginOption>} opts
*/
loadPlugins(plugins = [], opts = {}) {
this._pluginListLoaded = true;
plugins.forEach(path => {
const url = this._urlFor(path);
// Skip if preloaded, for bundling.
if (this.isPluginPreloaded(url)) return;
const pluginKey = this._getPluginKeyFromUrl(url);
// Skip if already installed.
if (this._plugins.has(pluginKey)) return;
this._plugins.set(pluginKey, {
name: pluginKey,
url,
state: PluginState.PENDING,
plugin: null,
});
if (this._isPathEndsWith(url, '.html')) {
this._importHtmlPlugin(url, opts && opts[path]);
} else if (this._isPathEndsWith(url, '.js')) {
this._loadJsPlugin(url);
} else {
this._failToLoad(`Unrecognized plugin url ${url}`, url);
}
});
this.awaitPluginsLoaded().then(() => {
console.info('Plugins loaded');
this._getReporting().pluginsLoaded(this._getAllInstalledPluginNames());
});
}
_isPathEndsWith(url, suffix) {
if (!(url instanceof URL)) {
try {
url = new URL(url);
} catch (e) {
console.warn(e);
return false;
}
}
return url.pathname && url.pathname.endsWith(suffix);
}
_getAllInstalledPluginNames() {
const installedPlugins = [];
for (const plugin of this._plugins.values()) {
if (plugin.state === PluginState.LOADED) {
installedPlugins.push(plugin.name);
}
}
return installedPlugins;
}
install(callback, opt_version, opt_src) {
// HTML import polyfill adds __importElement pointing to the import tag.
const script = document.currentScript &&
(document.currentScript.__importElement || document.currentScript);
let src = opt_src || (script && script.src);
if (!src || src.startsWith('data:')) {
src = script && script.baseURI;
}
if (opt_version && opt_version !== API_VERSION) {
this._failToLoad(`Plugin ${src} install error: only version ` +
API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
' was given.', src);
return;
}
const pluginObject = this.getPlugin(src);
let plugin = pluginObject && pluginObject.plugin;
if (!plugin) {
plugin = new Plugin(src);
}
try {
callback(plugin);
this._pluginInstalled(src, plugin);
} catch (e) {
this._failToLoad(`${e.name}: ${e.message}`, src);
}
}
get arePluginsLoaded() {
// As the size of plugins is relatively small,
// so the performance of this check should be reasonable
if (!this._pluginListLoaded) return false;
for (const plugin of this._plugins.values()) {
if (plugin.state === PluginState.PENDING) return false;
}
return true;
}
_checkIfCompleted() {
if (this.arePluginsLoaded && this._loadingResolver) {
this._loadingResolver();
this._loadingResolver = null;
this._loadingPromise = null;
}
}
_timeout() {
const pendingPlugins = [];
for (const plugin of this._plugins.values()) {
if (plugin.state === PluginState.PENDING) {
this._updatePluginState(plugin.url, PluginState.LOAD_FAILED);
this._checkIfCompleted();
pendingPlugins.push(plugin.url);
}
}
return `Timeout when loading plugins: ${pendingPlugins.join(',')}`;
}
_failToLoad(message, pluginUrl) {
// Show an alert with the error
document.dispatchEvent(new CustomEvent('show-alert', {
detail: {
message: `Plugin install error: ${message} from ${pluginUrl}`,
},
}));
this._updatePluginState(pluginUrl, PluginState.LOAD_FAILED);
this._checkIfCompleted();
}
_updatePluginState(pluginUrl, state) {
const key = this._getPluginKeyFromUrl(pluginUrl);
if (this._plugins.has(key)) {
this._plugins.get(key).state = state;
} else {
// Plugin is not recorded for some reason.
console.warn(`Plugin loaded separately: ${pluginUrl}`);
this._plugins.set(key, {
name: key,
url: pluginUrl,
state,
plugin: null,
});
}
return this._plugins.get(key);
}
_pluginInstalled(url, plugin) {
const pluginObj = this._updatePluginState(url, PluginState.LOADED);
pluginObj.plugin = plugin;
this._getReporting().pluginLoaded(plugin.getPluginName() || url);
console.log(`Plugin ${plugin.getPluginName() || url} installed.`);
this._checkIfCompleted();
}
installPreloadedPlugins() {
if (!window.Gerrit || !window.Gerrit._preloadedPlugins) { return; }
const Gerrit = window.Gerrit;
for (const name in Gerrit._preloadedPlugins) {
if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
const callback = Gerrit._preloadedPlugins[name];
this.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
}
}
isPluginPreloaded(pathOrUrl) {
const url = this._urlFor(pathOrUrl);
const name = getPluginNameFromUrl(url);
if (name && window.Gerrit._preloadedPlugins) {
return window.Gerrit._preloadedPlugins.hasOwnProperty(name);
} else {
return false;
}
}
/**
* Checks if given plugin path/url is enabled or not.
* @param {string} pathOrUrl
*/
isPluginEnabled(pathOrUrl) {
const url = this._urlFor(pathOrUrl);
if (this.isPluginPreloaded(url)) return true;
const key = this._getPluginKeyFromUrl(url);
return this._plugins.has(key);
}
/**
* Returns the plugin object with a given url.
* @param {string} pathOrUrl
*/
getPlugin(pathOrUrl) {
const key = this._getPluginKeyFromUrl(this._urlFor(pathOrUrl));
return this._plugins.get(key);
}
/**
* Checks if given plugin path/url is loaded or not.
* @param {string} pathOrUrl
*/
isPluginLoaded(pathOrUrl) {
const url = this._urlFor(pathOrUrl);
const key = this._getPluginKeyFromUrl(url);
return this._plugins.has(key) ?
this._plugins.get(key).state === PluginState.LOADED :
false;
}
_importHtmlPlugin(pluginUrl, opts = {}) {
// onload (second param) needs to be a function. When null or undefined
// were passed, plugins were not loaded correctly.
(Polymer.importHref || Polymer.Base.importHref)(
this._urlFor(pluginUrl), () => {},
() => this._failToLoad(`${pluginUrl} import error`, pluginUrl),
!opts.sync);
}
_loadJsPlugin(pluginUrl) {
this._createScriptTag(this._urlFor(pluginUrl));
}
_createScriptTag(url) {
const el = document.createElement('script');
el.defer = true;
el.src = url;
el.onerror = () => this._failToLoad(`${url} load error`, url);
return document.body.appendChild(el);
}
_urlFor(pathOrUrl) {
if (!pathOrUrl) {
return pathOrUrl;
}
if (pathOrUrl.startsWith(PRELOADED_PROTOCOL) ||
pathOrUrl.startsWith('http')) {
// Plugins are loaded from another domain or preloaded.
return pathOrUrl;
}
if (!pathOrUrl.startsWith('/')) {
pathOrUrl = '/' + pathOrUrl;
}
return window.location.origin + getBaseUrl() + pathOrUrl;
}
awaitPluginsLoaded() {
// Resolve if completed.
this._checkIfCompleted();
if (this.arePluginsLoaded) {
return Promise.resolve();
}
if (!this._loadingPromise) {
let timerId;
this._loadingPromise =
Promise.race([
new Promise(resolve => this._loadingResolver = resolve),
new Promise((_, reject) => timerId = setTimeout(
() => {
reject(this._timeout());
}, PLUGIN_LOADING_TIMEOUT_MS)),
]).then(() => {
if (timerId) clearTimeout(timerId);
});
}
return this._loadingPromise;
}
}
/**
* @typedef {{
* name:string,
* url:string,
* state:PluginState,
* plugin:Object
* }}
*/
PluginLoader.PluginObject;
/**
* @typedef {{
* sync:boolean,
* }}
*/
PluginLoader.PluginOption;
window.PluginLoader = PluginLoader;
})(window);

View File

@@ -0,0 +1,502 @@
<!DOCTYPE html>
<!--
@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.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-plugin-host</title>
<script src="/test/common-test-setup.js"></script>
<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<script src="/bower_components/web-component-tester/browser.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-js-api-interface.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-js-api-interface></gr-js-api-interface>
</template>
</test-fixture>
<script>
const {PRELOADED_PROTOCOL, PLUGIN_LOADING_TIMEOUT_MS} = window._apiUtils;
suite('gr-plugin-loader tests', () => {
let plugin;
let sandbox;
let url;
let sendStub;
setup(() => {
this.clock = sinon.useFakeTimers();
sandbox = sinon.sandbox.create();
sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
stub('gr-rest-api-interface', {
getAccount() {
return Promise.resolve({name: 'Judy Hopps'});
},
send(...args) {
return sendStub(...args);
},
});
sandbox.stub(document.body, 'appendChild');
fixture('basic');
url = window.location.origin;
});
teardown(() => {
sandbox.restore();
this.clock.restore();
Gerrit._testOnly_resetPlugins();
});
test('reuse plugin for install calls', () => {
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
let otherPlugin;
Gerrit.install(p => { otherPlugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
assert.strictEqual(plugin, otherPlugin);
});
test('flushes preinstalls if provided', () => {
assert.doesNotThrow(() => {
Gerrit._testOnly_flushPreinstalls();
});
window.Gerrit.flushPreinstalls = sandbox.stub();
Gerrit._testOnly_flushPreinstalls();
assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
delete window.Gerrit.flushPreinstalls;
});
test('versioning', () => {
const callback = sandbox.spy();
Gerrit.install(callback, '0.0pre-alpha');
assert(callback.notCalled);
});
test('report pluginsLoaded', done => {
stub('gr-reporting', {
pluginsLoaded() {
done();
},
});
Gerrit._loadPlugins([]);
});
test('arePluginsLoaded', done => {
assert.isFalse(Gerrit._arePluginsLoaded());
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
];
Gerrit._loadPlugins(plugins);
assert.isFalse(Gerrit._arePluginsLoaded());
// Timeout on loading plugins
this.clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
flush(() => {
assert.isTrue(Gerrit._arePluginsLoaded());
done();
});
});
test('plugins installed successfully', done => {
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => void 0, undefined, url);
});
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
});
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
];
Gerrit._loadPlugins(plugins);
flush(() => {
assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
assert.isTrue(Gerrit._arePluginsLoaded());
done();
});
});
test('isPluginEnabled and isPluginLoaded', done => {
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => void 0, undefined, url);
});
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
});
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
'bar/static/test.js',
];
Gerrit._loadPlugins(plugins);
assert.isTrue(
plugins.every(plugin => Gerrit._pluginLoader.isPluginEnabled(plugin))
);
flush(() => {
assert.isTrue(Gerrit._arePluginsLoaded());
assert.isTrue(
plugins.every(plugin => Gerrit._pluginLoader.isPluginLoaded(plugin))
);
done();
});
});
test('plugins installed mixed result, 1 fail 1 succeed', done => {
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
];
const alertStub = sandbox.stub();
document.addEventListener('show-alert', alertStub);
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => {
if (url === plugins[0]) {
throw new Error('failed');
}
}, undefined, url);
});
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
});
Gerrit._loadPlugins(plugins);
flush(() => {
assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
assert.isTrue(Gerrit._arePluginsLoaded());
assert.isTrue(alertStub.calledOnce);
done();
});
});
test('isPluginEnabled and isPluginLoaded for mixed results', done => {
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
];
const alertStub = sandbox.stub();
document.addEventListener('show-alert', alertStub);
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => {
if (url === plugins[0]) {
throw new Error('failed');
}
}, undefined, url);
});
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
});
Gerrit._loadPlugins(plugins);
assert.isTrue(
plugins.every(plugin => Gerrit._pluginLoader.isPluginEnabled(plugin))
);
flush(() => {
assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
assert.isTrue(Gerrit._arePluginsLoaded());
assert.isTrue(alertStub.calledOnce);
assert.isTrue(Gerrit._pluginLoader.isPluginLoaded(plugins[1]));
assert.isFalse(Gerrit._pluginLoader.isPluginLoaded(plugins[0]));
done();
});
});
test('plugins installed all failed', done => {
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
];
const alertStub = sandbox.stub();
document.addEventListener('show-alert', alertStub);
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => {
throw new Error('failed');
}, undefined, url);
});
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
});
Gerrit._loadPlugins(plugins);
flush(() => {
assert.isTrue(pluginsLoadedStub.calledWithExactly([]));
assert.isTrue(Gerrit._arePluginsLoaded());
assert.isTrue(alertStub.calledTwice);
done();
});
});
test('plugins installed failed becasue of wrong version', done => {
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/bar/static/test.js',
];
const alertStub = sandbox.stub();
document.addEventListener('show-alert', alertStub);
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => {
}, url === plugins[0] ? '' : 'alpha', url);
});
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
});
Gerrit._loadPlugins(plugins);
flush(() => {
assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo']));
assert.isTrue(Gerrit._arePluginsLoaded());
assert.isTrue(alertStub.calledOnce);
done();
});
});
test('multiple assets for same plugin installed successfully', done => {
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => void 0, undefined, url);
});
const pluginsLoadedStub = sandbox.stub();
stub('gr-reporting', {
pluginsLoaded: (...args) => pluginsLoadedStub(...args),
});
const plugins = [
'http://test.com/plugins/foo/static/test.js',
'http://test.com/plugins/foo/static/test2.js',
'http://test.com/plugins/bar/static/test.js',
];
Gerrit._loadPlugins(plugins);
flush(() => {
assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
assert.isTrue(Gerrit._arePluginsLoaded());
done();
});
});
suite('plugin path and url', () => {
let importHtmlPluginStub;
let loadJsPluginStub;
setup(() => {
importHtmlPluginStub = sandbox.stub();
sandbox.stub(Gerrit._pluginLoader, '_importHtmlPlugin', url => {
importHtmlPluginStub(url);
});
loadJsPluginStub = sandbox.stub();
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
loadJsPluginStub(url);
});
});
test('invalid plugin path', () => {
const failToLoadStub = sandbox.stub();
sandbox.stub(Gerrit._pluginLoader, '_failToLoad', (...args) => {
failToLoadStub(...args);
});
Gerrit._loadPlugins([
'foo/bar',
]);
assert.isTrue(failToLoadStub.calledOnce);
assert.isTrue(failToLoadStub.calledWithExactly(
`Unrecognized plugin url ${url}/foo/bar`,
`${url}/foo/bar`
));
});
test('relative path for plugins', () => {
Gerrit._loadPlugins([
'foo/bar.js',
'foo/bar.html',
]);
assert.isTrue(importHtmlPluginStub.calledOnce);
assert.isTrue(
importHtmlPluginStub.calledWithExactly(`${url}/foo/bar.html`)
);
assert.isTrue(loadJsPluginStub.calledOnce);
assert.isTrue(
loadJsPluginStub.calledWithExactly(`${url}/foo/bar.js`)
);
});
test('relative path should honor getBaseUrl', () => {
const testUrl = '/test';
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl', () => {
return testUrl;
});
Gerrit._loadPlugins([
'foo/bar.js',
'foo/bar.html',
]);
assert.isTrue(importHtmlPluginStub.calledOnce);
assert.isTrue(loadJsPluginStub.calledOnce);
assert.isTrue(
importHtmlPluginStub.calledWithExactly(
`${url}${testUrl}/foo/bar.html`
)
);
assert.isTrue(
loadJsPluginStub.calledWithExactly(`${url}${testUrl}/foo/bar.js`)
);
});
test('absolute path for plugins', () => {
Gerrit._loadPlugins([
'http://e.com/foo/bar.js',
'http://e.com/foo/bar.html',
]);
assert.isTrue(importHtmlPluginStub.calledOnce);
assert.isTrue(
importHtmlPluginStub.calledWithExactly(`http://e.com/foo/bar.html`)
);
assert.isTrue(loadJsPluginStub.calledOnce);
assert.isTrue(
loadJsPluginStub.calledWithExactly(`http://e.com/foo/bar.js`)
);
});
});
test('adds js plugins will call the body', () => {
Gerrit._loadPlugins([
'http://e.com/foo/bar.js',
'http://e.com/bar/foo.js',
]);
assert.isTrue(document.body.appendChild.calledTwice);
});
test('can call awaitPluginsLoaded multiple times', done => {
const plugins = [
'http://e.com/foo/bar.js',
'http://e.com/bar/foo.js',
];
let installed = false;
function pluginCallback(url) {
if (url === plugins[1]) {
installed = true;
}
}
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
Gerrit.install(() => pluginCallback(url), undefined, url);
});
Gerrit._loadPlugins(plugins);
Gerrit.awaitPluginsLoaded().then(() => {
assert.isTrue(installed);
Gerrit.awaitPluginsLoaded().then(() => {
done();
});
});
});
suite('preloaded plugins', () => {
test('skips preloaded plugins when load plugins', () => {
const importHtmlPluginStub = sandbox.stub();
sandbox.stub(Gerrit._pluginLoader, '_importHtmlPlugin', url => {
importHtmlPluginStub(url);
});
const loadJsPluginStub = sandbox.stub();
sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
loadJsPluginStub(url);
});
Gerrit._preloadedPlugins = {
foo: () => void 0,
bar: () => void 0,
};
Gerrit._loadPlugins([
'http://e.com/plugins/foo.js',
'plugins/bar.html',
'http://e.com/plugins/test/foo.js',
]);
assert.isTrue(importHtmlPluginStub.notCalled);
assert.isTrue(loadJsPluginStub.calledOnce);
});
test('isPluginPreloaded', () => {
Gerrit._preloadedPlugins = {baz: ()=>{}};
assert.isFalse(Gerrit._pluginLoader.isPluginPreloaded('plugins/foo/bar'));
assert.isFalse(Gerrit._pluginLoader.isPluginPreloaded('http://a.com/42'));
assert.isTrue(
Gerrit._pluginLoader.isPluginPreloaded(PRELOADED_PROTOCOL + 'baz')
);
Gerrit._preloadedPlugins = null;
});
test('preloaded plugins are installed', () => {
const installStub = sandbox.stub();
Gerrit._preloadedPlugins = {foo: installStub};
Gerrit._pluginLoader.installPreloadedPlugins();
assert.isTrue(installStub.called);
const pluginApi = installStub.lastCall.args[0];
assert.strictEqual(pluginApi.getPluginName(), 'foo');
});
test('installing preloaded plugin', () => {
let plugin;
window.ASSETS_PATH = 'http://blips.com/chitz';
Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
assert.strictEqual(plugin.getPluginName(), 'foo');
assert.strictEqual(plugin.url('/some/thing.html'),
'http://blips.com/chitz/plugins/foo/some/thing.html');
delete window.ASSETS_PATH;
});
});
});
</script>

View File

@@ -50,7 +50,6 @@ limitations under the License.
a[k] = (...args) => restApiStub[k](...args);
return a;
}, {}));
Gerrit._setPluginsCount(1);
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
instance = new GrPluginRestApi();

View File

@@ -267,10 +267,14 @@
return;
}
return this.registerCustomComponent(
Gerrit._getPluginScreenName(this.getPluginName(), screenName),
this._getScreenName(screenName),
opt_moduleName);
};
Plugin.prototype._getScreenName = function(screenName) {
return `${this.getPluginName()}-screen-${screenName}`;
};
const deprecatedAPI = {
_loadedGwt: ()=> {},
@@ -321,7 +325,7 @@
'Please use strings for patterns.');
return;
}
this.hook(Gerrit._getPluginScreenName(this.getPluginName(), pattern))
this.hook(this._getScreenName(pattern))
.onAttached(el => {
el.style.display = 'none';
callback({

View File

@@ -188,6 +188,7 @@ limitations under the License.
'shared/gr-js-api-interface/gr-js-api-interface_test.html',
'shared/gr-js-api-interface/gr-gerrit_test.html',
'shared/gr-js-api-interface/gr-plugin-action-context_test.html',
'shared/gr-js-api-interface/gr-plugin-loader_test.html',
'shared/gr-js-api-interface/gr-plugin-endpoints_test.html',
'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
'shared/gr-fixed-panel/gr-fixed-panel_test.html',