Generalize library loader
Until recently, the only side-loaded resource was the HLJS library for computing syntax highlighting. For this task, the gr-syntax-lib-loader provided an interface to load the library whether or not PG is being served from a CDN. With this change, the component is refactored to allow loading resources other than the syntax library. A method is added for loading the "dark-theme" document independently of whether a CDN is configured. Also, some documentation comments are added to the existing methods. Change-Id: I9891539cd4cf76ac0fe430ff3988e3a9dfbb0ca3
This commit is contained in:
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../gr-syntax-lib-loader/gr-syntax-lib-loader.html">
|
||||
<link rel="import" href="../../shared/gr-lib-loader/gr-lib-loader.html">
|
||||
|
||||
<dom-module id="gr-syntax-layer">
|
||||
<template>
|
||||
<gr-syntax-lib-loader id="libLoader"></gr-syntax-lib-loader>
|
||||
<gr-lib-loader id="libLoader"></gr-lib-loader>
|
||||
</template>
|
||||
<script src="../gr-diff/gr-diff-line.js"></script>
|
||||
<script src="../gr-diff-highlight/gr-annotation.js"></script>
|
||||
|
@@ -442,7 +442,7 @@
|
||||
},
|
||||
|
||||
_loadHLJS() {
|
||||
return this.$.libLoader.get().then(hljs => {
|
||||
return this.$.libLoader.getHLJS().then(hljs => {
|
||||
this._hljs = hljs;
|
||||
});
|
||||
},
|
||||
|
@@ -185,7 +185,7 @@ limitations under the License.
|
||||
|
||||
const mockHLJS = getMockHLJS();
|
||||
const highlightSpy = sinon.spy(mockHLJS, 'highlight');
|
||||
sandbox.stub(element.$.libLoader, 'get',
|
||||
sandbox.stub(element.$.libLoader, 'getHLJS',
|
||||
() => { return Promise.resolve(mockHLJS); });
|
||||
const processNextSpy = sandbox.spy(element, '_processNextLine');
|
||||
const processPromise = element.process();
|
||||
|
@@ -56,6 +56,7 @@ limitations under the License.
|
||||
<link rel="import" href="./settings/gr-registration-dialog/gr-registration-dialog.html">
|
||||
<link rel="import" href="./settings/gr-settings-view/gr-settings-view.html">
|
||||
<link rel="import" href="./shared/gr-fixed-panel/gr-fixed-panel.html">
|
||||
<link rel="import" href="./shared/gr-lib-loader/gr-lib-loader.html">
|
||||
<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
|
||||
<script src="../scripts/util.js"></script>
|
||||
@@ -229,6 +230,7 @@ limitations under the License.
|
||||
<gr-plugin-host id="plugins"
|
||||
config="[[_serverConfig]]">
|
||||
</gr-plugin-host>
|
||||
<gr-lib-loader id="libLoader"></gr-lib-loader>
|
||||
<gr-external-style id="externalStyle" name="app-theme"></gr-external-style>
|
||||
</template>
|
||||
<script src="gr-app.js" crossorigin="anonymous"></script>
|
||||
|
@@ -128,7 +128,7 @@
|
||||
});
|
||||
|
||||
if (window.localStorage.getItem('dark-theme')) {
|
||||
this.importHref('../styles/themes/dark-theme.html');
|
||||
this.$.libLoader.loadDarkTheme();
|
||||
}
|
||||
|
||||
// Note: this is evaluated here to ensure that it only happens after the
|
||||
|
@@ -16,6 +16,6 @@ limitations under the License.
|
||||
-->
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="gr-syntax-lib-loader">
|
||||
<script src="gr-syntax-lib-loader.js"></script>
|
||||
<dom-module id="gr-lib-loader">
|
||||
<script src="gr-lib-loader.js"></script>
|
||||
</dom-module>
|
@@ -18,13 +18,14 @@
|
||||
'use strict';
|
||||
|
||||
const HLJS_PATH = 'bower_components/highlightjs/highlight.min.js';
|
||||
const DARK_THEME_PATH = 'styles/themes/dark-theme.html';
|
||||
const LIB_ROOT_PATTERN = /(.+\/)elements\/gr-app\.html/;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-syntax-lib-loader',
|
||||
is: 'gr-lib-loader',
|
||||
|
||||
properties: {
|
||||
_state: {
|
||||
_hljsState: {
|
||||
type: Object,
|
||||
|
||||
// NOTE: intended singleton.
|
||||
@@ -36,7 +37,13 @@
|
||||
},
|
||||
},
|
||||
|
||||
get() {
|
||||
/**
|
||||
* Get the HLJS library. Returns a promise that resolves with a reference to
|
||||
* the library after it's been loaded. The promise resolves immediately if
|
||||
* it's already been loaded.
|
||||
* @return {!Promise<Object>}
|
||||
*/
|
||||
getHLJS() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// If the lib is totally loaded, resolve immediately.
|
||||
if (this._getHighlightLib()) {
|
||||
@@ -45,55 +52,87 @@
|
||||
}
|
||||
|
||||
// If the library is not currently being loaded, then start loading it.
|
||||
if (!this._state.loading) {
|
||||
this._state.loading = true;
|
||||
this._loadHLJS().then(this._onLibLoaded.bind(this)).catch(reject);
|
||||
if (!this._hljsState.loading) {
|
||||
this._hljsState.loading = true;
|
||||
this._loadScript(this._getHLJSUrl())
|
||||
.then(this._onHLJSLibLoaded.bind(this)).catch(reject);
|
||||
}
|
||||
|
||||
this._state.callbacks.push(resolve);
|
||||
this._hljsState.callbacks.push(resolve);
|
||||
});
|
||||
},
|
||||
|
||||
_onLibLoaded() {
|
||||
const lib = this._getHighlightLib();
|
||||
this._state.loading = false;
|
||||
for (const cb of this._state.callbacks) {
|
||||
cb(lib);
|
||||
}
|
||||
this._state.callbacks = [];
|
||||
/**
|
||||
* Load and apply the dark theme document.
|
||||
*/
|
||||
loadDarkTheme() {
|
||||
this.importHref(this._getLibRoot() + DARK_THEME_PATH);
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute callbacks awaiting the HLJS lib load.
|
||||
*/
|
||||
_onHLJSLibLoaded() {
|
||||
const lib = this._getHighlightLib();
|
||||
this._hljsState.loading = false;
|
||||
for (const cb of this._hljsState.callbacks) {
|
||||
cb(lib);
|
||||
}
|
||||
this._hljsState.callbacks = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the HLJS library, assuming it has been loaded. Configure the library
|
||||
* if it hasn't already been configured.
|
||||
* @return {!Object}
|
||||
*/
|
||||
_getHighlightLib() {
|
||||
const lib = window.hljs;
|
||||
if (lib && !this._state.configured) {
|
||||
this._state.configured = true;
|
||||
if (lib && !this._hljsState.configured) {
|
||||
this._hljsState.configured = true;
|
||||
|
||||
lib.configure({classPrefix: 'gr-diff gr-syntax gr-syntax-'});
|
||||
}
|
||||
return lib;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the resource path used to load the application. If the application
|
||||
* was loaded through a CDN, then this will be the path to CDN resources.
|
||||
* @return {string}
|
||||
*/
|
||||
_getLibRoot() {
|
||||
if (this._cachedLibRoot) { return this._cachedLibRoot; }
|
||||
|
||||
const appLink = document.head
|
||||
.querySelector('link[rel=import][href$="gr-app.html"]');
|
||||
|
||||
if (!appLink) { return null; }
|
||||
if (!appLink) { throw new Error('Could not find application link'); }
|
||||
|
||||
return this._cachedLibRoot = appLink
|
||||
this._cachedLibRoot = appLink
|
||||
.href
|
||||
.match(LIB_ROOT_PATTERN)[1];
|
||||
|
||||
if (!this._cachedLibRoot) {
|
||||
throw new Error('Could not extract lib root');
|
||||
}
|
||||
|
||||
return this._cachedLibRoot;
|
||||
},
|
||||
_cachedLibRoot: null,
|
||||
|
||||
_loadHLJS() {
|
||||
/**
|
||||
* Load and execute a JS file from the lib root.
|
||||
* @param {string} src The path to the JS file without the lib root.
|
||||
* @return {Promise} a promise that resolves when the script's onload
|
||||
* executes.
|
||||
*/
|
||||
_loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
const src = this._getHLJSUrl();
|
||||
|
||||
if (!src) {
|
||||
reject(new Error('Unable to load blank HLJS url.'));
|
||||
reject(new Error('Unable to load blank script url.'));
|
||||
return;
|
||||
}
|
||||
|
@@ -17,64 +17,67 @@ limitations under the License.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<title>gr-syntax-lib-loader</title>
|
||||
<title>gr-lib-loader</title>
|
||||
|
||||
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.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-syntax-lib-loader.html">
|
||||
<link rel="import" href="gr-lib-loader.html">
|
||||
|
||||
<script>void(0);</script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-syntax-lib-loader></gr-syntax-lib-loader>
|
||||
<gr-lib-loader></gr-lib-loader>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script>
|
||||
suite('gr-syntax-lib-loader tests', () => {
|
||||
suite('gr-lib-loader tests', () => {
|
||||
let sandbox;
|
||||
let element;
|
||||
let resolveLoad;
|
||||
let loadStub;
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
element = fixture('basic');
|
||||
|
||||
loadStub = sinon.stub(element, '_loadHLJS', () =>
|
||||
loadStub = sandbox.stub(element, '_loadScript', () =>
|
||||
new Promise(resolve => resolveLoad = resolve)
|
||||
);
|
||||
|
||||
// Assert preconditions:
|
||||
assert.isFalse(element._state.loading);
|
||||
assert.isFalse(element._hljsState.loading);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
if (window.hljs) {
|
||||
delete window.hljs;
|
||||
}
|
||||
loadStub.restore();
|
||||
sandbox.restore();
|
||||
|
||||
// Because the element state is a singleton, clean it up.
|
||||
element._state.configured = false;
|
||||
element._state.loading = false;
|
||||
element._state.callbacks = [];
|
||||
element._hljsState.configured = false;
|
||||
element._hljsState.loading = false;
|
||||
element._hljsState.callbacks = [];
|
||||
});
|
||||
|
||||
test('only load once', done => {
|
||||
sandbox.stub(element, '_getHLJSUrl').returns('');
|
||||
const firstCallHandler = sinon.stub();
|
||||
element.get().then(firstCallHandler);
|
||||
element.getHLJS().then(firstCallHandler);
|
||||
|
||||
// It should now be in the loading state.
|
||||
assert.isTrue(loadStub.called);
|
||||
assert.isTrue(element._state.loading);
|
||||
assert.isTrue(element._hljsState.loading);
|
||||
assert.isFalse(firstCallHandler.called);
|
||||
|
||||
const secondCallHandler = sinon.stub();
|
||||
element.get().then(secondCallHandler);
|
||||
element.getHLJS().then(secondCallHandler);
|
||||
|
||||
// No change in state.
|
||||
assert.isTrue(element._state.loading);
|
||||
assert.isTrue(element._hljsState.loading);
|
||||
assert.isFalse(firstCallHandler.called);
|
||||
assert.isFalse(secondCallHandler.called);
|
||||
|
||||
@@ -82,7 +85,7 @@ limitations under the License.
|
||||
resolveLoad();
|
||||
flush(() => {
|
||||
// The state should be loaded and both handlers called.
|
||||
assert.isFalse(element._state.loading);
|
||||
assert.isFalse(element._hljsState.loading);
|
||||
assert.isTrue(firstCallHandler.called);
|
||||
assert.isTrue(secondCallHandler.called);
|
||||
done();
|
||||
@@ -105,7 +108,7 @@ limitations under the License.
|
||||
|
||||
test('returns hljs', done => {
|
||||
const firstCallHandler = sinon.stub();
|
||||
element.get().then(firstCallHandler);
|
||||
element.getHLJS().then(firstCallHandler);
|
||||
flush(() => {
|
||||
assert.isTrue(firstCallHandler.called);
|
||||
assert.isTrue(firstCallHandler.calledWith(hljsStub));
|
||||
@@ -114,7 +117,7 @@ limitations under the License.
|
||||
});
|
||||
|
||||
test('configures hljs', done => {
|
||||
element.get().then(() => {
|
||||
element.getHLJS().then(() => {
|
||||
assert.isTrue(window.hljs.configure.calledOnce);
|
||||
done();
|
||||
});
|
||||
@@ -123,15 +126,10 @@ limitations under the License.
|
||||
|
||||
suite('_getHLJSUrl', () => {
|
||||
suite('checking _getLibRoot', () => {
|
||||
let libRootStub;
|
||||
let root;
|
||||
|
||||
setup(() => {
|
||||
libRootStub = sinon.stub(element, '_getLibRoot', () => root);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
libRootStub.restore();
|
||||
sandbox.stub(element, '_getLibRoot', () => root);
|
||||
});
|
||||
|
||||
test('with no root', () => {
|
@@ -112,7 +112,6 @@ limitations under the License.
|
||||
'diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html',
|
||||
'diff/gr-selection-action-box/gr-selection-action-box_test.html',
|
||||
'diff/gr-syntax-layer/gr-syntax-layer_test.html',
|
||||
'diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html',
|
||||
'edit/gr-default-editor/gr-default-editor_test.html',
|
||||
'edit/gr-edit-controls/gr-edit-controls_test.html',
|
||||
'edit/gr-edit-file-controls/gr-edit-file-controls_test.html',
|
||||
@@ -165,6 +164,7 @@ limitations under the License.
|
||||
'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',
|
||||
'shared/gr-lib-loader/gr-lib-loader_test.html',
|
||||
'shared/gr-limited-text/gr-limited-text_test.html',
|
||||
'shared/gr-linked-chip/gr-linked-chip_test.html',
|
||||
'shared/gr-linked-text/gr-linked-text_test.html',
|
||||
|
Reference in New Issue
Block a user