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.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
<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">
|
<dom-module id="gr-syntax-layer">
|
||||||
<template>
|
<template>
|
||||||
<gr-syntax-lib-loader id="libLoader"></gr-syntax-lib-loader>
|
<gr-lib-loader id="libLoader"></gr-lib-loader>
|
||||||
</template>
|
</template>
|
||||||
<script src="../gr-diff/gr-diff-line.js"></script>
|
<script src="../gr-diff/gr-diff-line.js"></script>
|
||||||
<script src="../gr-diff-highlight/gr-annotation.js"></script>
|
<script src="../gr-diff-highlight/gr-annotation.js"></script>
|
||||||
|
@@ -442,7 +442,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
_loadHLJS() {
|
_loadHLJS() {
|
||||||
return this.$.libLoader.get().then(hljs => {
|
return this.$.libLoader.getHLJS().then(hljs => {
|
||||||
this._hljs = hljs;
|
this._hljs = hljs;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -185,7 +185,7 @@ limitations under the License.
|
|||||||
|
|
||||||
const mockHLJS = getMockHLJS();
|
const mockHLJS = getMockHLJS();
|
||||||
const highlightSpy = sinon.spy(mockHLJS, 'highlight');
|
const highlightSpy = sinon.spy(mockHLJS, 'highlight');
|
||||||
sandbox.stub(element.$.libLoader, 'get',
|
sandbox.stub(element.$.libLoader, 'getHLJS',
|
||||||
() => { return Promise.resolve(mockHLJS); });
|
() => { return Promise.resolve(mockHLJS); });
|
||||||
const processNextSpy = sandbox.spy(element, '_processNextLine');
|
const processNextSpy = sandbox.spy(element, '_processNextLine');
|
||||||
const processPromise = element.process();
|
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-registration-dialog/gr-registration-dialog.html">
|
||||||
<link rel="import" href="./settings/gr-settings-view/gr-settings-view.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-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">
|
<link rel="import" href="./shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||||
|
|
||||||
<script src="../scripts/util.js"></script>
|
<script src="../scripts/util.js"></script>
|
||||||
@@ -229,6 +230,7 @@ limitations under the License.
|
|||||||
<gr-plugin-host id="plugins"
|
<gr-plugin-host id="plugins"
|
||||||
config="[[_serverConfig]]">
|
config="[[_serverConfig]]">
|
||||||
</gr-plugin-host>
|
</gr-plugin-host>
|
||||||
|
<gr-lib-loader id="libLoader"></gr-lib-loader>
|
||||||
<gr-external-style id="externalStyle" name="app-theme"></gr-external-style>
|
<gr-external-style id="externalStyle" name="app-theme"></gr-external-style>
|
||||||
</template>
|
</template>
|
||||||
<script src="gr-app.js" crossorigin="anonymous"></script>
|
<script src="gr-app.js" crossorigin="anonymous"></script>
|
||||||
|
@@ -128,7 +128,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (window.localStorage.getItem('dark-theme')) {
|
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
|
// 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">
|
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||||
|
|
||||||
<dom-module id="gr-syntax-lib-loader">
|
<dom-module id="gr-lib-loader">
|
||||||
<script src="gr-syntax-lib-loader.js"></script>
|
<script src="gr-lib-loader.js"></script>
|
||||||
</dom-module>
|
</dom-module>
|
@@ -18,13 +18,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const HLJS_PATH = 'bower_components/highlightjs/highlight.min.js';
|
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/;
|
const LIB_ROOT_PATTERN = /(.+\/)elements\/gr-app\.html/;
|
||||||
|
|
||||||
Polymer({
|
Polymer({
|
||||||
is: 'gr-syntax-lib-loader',
|
is: 'gr-lib-loader',
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
_state: {
|
_hljsState: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
||||||
// NOTE: intended singleton.
|
// 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
// If the lib is totally loaded, resolve immediately.
|
// If the lib is totally loaded, resolve immediately.
|
||||||
if (this._getHighlightLib()) {
|
if (this._getHighlightLib()) {
|
||||||
@@ -45,55 +52,87 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the library is not currently being loaded, then start loading it.
|
// If the library is not currently being loaded, then start loading it.
|
||||||
if (!this._state.loading) {
|
if (!this._hljsState.loading) {
|
||||||
this._state.loading = true;
|
this._hljsState.loading = true;
|
||||||
this._loadHLJS().then(this._onLibLoaded.bind(this)).catch(reject);
|
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();
|
* Load and apply the dark theme document.
|
||||||
this._state.loading = false;
|
*/
|
||||||
for (const cb of this._state.callbacks) {
|
loadDarkTheme() {
|
||||||
cb(lib);
|
this.importHref(this._getLibRoot() + DARK_THEME_PATH);
|
||||||
}
|
|
||||||
this._state.callbacks = [];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
_getHighlightLib() {
|
||||||
const lib = window.hljs;
|
const lib = window.hljs;
|
||||||
if (lib && !this._state.configured) {
|
if (lib && !this._hljsState.configured) {
|
||||||
this._state.configured = true;
|
this._hljsState.configured = true;
|
||||||
|
|
||||||
lib.configure({classPrefix: 'gr-diff gr-syntax gr-syntax-'});
|
lib.configure({classPrefix: 'gr-diff gr-syntax gr-syntax-'});
|
||||||
}
|
}
|
||||||
return lib;
|
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() {
|
_getLibRoot() {
|
||||||
if (this._cachedLibRoot) { return this._cachedLibRoot; }
|
if (this._cachedLibRoot) { return this._cachedLibRoot; }
|
||||||
|
|
||||||
const appLink = document.head
|
const appLink = document.head
|
||||||
.querySelector('link[rel=import][href$="gr-app.html"]');
|
.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
|
.href
|
||||||
.match(LIB_ROOT_PATTERN)[1];
|
.match(LIB_ROOT_PATTERN)[1];
|
||||||
|
|
||||||
|
if (!this._cachedLibRoot) {
|
||||||
|
throw new Error('Could not extract lib root');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._cachedLibRoot;
|
||||||
},
|
},
|
||||||
_cachedLibRoot: null,
|
_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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
const src = this._getHLJSUrl();
|
|
||||||
|
|
||||||
if (!src) {
|
if (!src) {
|
||||||
reject(new Error('Unable to load blank HLJS url.'));
|
reject(new Error('Unable to load blank script url.'));
|
||||||
return;
|
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">
|
<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/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||||
<script src="../../../bower_components/web-component-tester/browser.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="../../../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>
|
<script>void(0);</script>
|
||||||
|
|
||||||
<test-fixture id="basic">
|
<test-fixture id="basic">
|
||||||
<template>
|
<template>
|
||||||
<gr-syntax-lib-loader></gr-syntax-lib-loader>
|
<gr-lib-loader></gr-lib-loader>
|
||||||
</template>
|
</template>
|
||||||
</test-fixture>
|
</test-fixture>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
suite('gr-syntax-lib-loader tests', () => {
|
suite('gr-lib-loader tests', () => {
|
||||||
|
let sandbox;
|
||||||
let element;
|
let element;
|
||||||
let resolveLoad;
|
let resolveLoad;
|
||||||
let loadStub;
|
let loadStub;
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
element = fixture('basic');
|
element = fixture('basic');
|
||||||
|
|
||||||
loadStub = sinon.stub(element, '_loadHLJS', () =>
|
loadStub = sandbox.stub(element, '_loadScript', () =>
|
||||||
new Promise(resolve => resolveLoad = resolve)
|
new Promise(resolve => resolveLoad = resolve)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert preconditions:
|
// Assert preconditions:
|
||||||
assert.isFalse(element._state.loading);
|
assert.isFalse(element._hljsState.loading);
|
||||||
});
|
});
|
||||||
|
|
||||||
teardown(() => {
|
teardown(() => {
|
||||||
if (window.hljs) {
|
if (window.hljs) {
|
||||||
delete window.hljs;
|
delete window.hljs;
|
||||||
}
|
}
|
||||||
loadStub.restore();
|
sandbox.restore();
|
||||||
|
|
||||||
// Because the element state is a singleton, clean it up.
|
// Because the element state is a singleton, clean it up.
|
||||||
element._state.configured = false;
|
element._hljsState.configured = false;
|
||||||
element._state.loading = false;
|
element._hljsState.loading = false;
|
||||||
element._state.callbacks = [];
|
element._hljsState.callbacks = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
test('only load once', done => {
|
test('only load once', done => {
|
||||||
|
sandbox.stub(element, '_getHLJSUrl').returns('');
|
||||||
const firstCallHandler = sinon.stub();
|
const firstCallHandler = sinon.stub();
|
||||||
element.get().then(firstCallHandler);
|
element.getHLJS().then(firstCallHandler);
|
||||||
|
|
||||||
// It should now be in the loading state.
|
// It should now be in the loading state.
|
||||||
assert.isTrue(loadStub.called);
|
assert.isTrue(loadStub.called);
|
||||||
assert.isTrue(element._state.loading);
|
assert.isTrue(element._hljsState.loading);
|
||||||
assert.isFalse(firstCallHandler.called);
|
assert.isFalse(firstCallHandler.called);
|
||||||
|
|
||||||
const secondCallHandler = sinon.stub();
|
const secondCallHandler = sinon.stub();
|
||||||
element.get().then(secondCallHandler);
|
element.getHLJS().then(secondCallHandler);
|
||||||
|
|
||||||
// No change in state.
|
// No change in state.
|
||||||
assert.isTrue(element._state.loading);
|
assert.isTrue(element._hljsState.loading);
|
||||||
assert.isFalse(firstCallHandler.called);
|
assert.isFalse(firstCallHandler.called);
|
||||||
assert.isFalse(secondCallHandler.called);
|
assert.isFalse(secondCallHandler.called);
|
||||||
|
|
||||||
@@ -82,7 +85,7 @@ limitations under the License.
|
|||||||
resolveLoad();
|
resolveLoad();
|
||||||
flush(() => {
|
flush(() => {
|
||||||
// The state should be loaded and both handlers called.
|
// 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(firstCallHandler.called);
|
||||||
assert.isTrue(secondCallHandler.called);
|
assert.isTrue(secondCallHandler.called);
|
||||||
done();
|
done();
|
||||||
@@ -105,7 +108,7 @@ limitations under the License.
|
|||||||
|
|
||||||
test('returns hljs', done => {
|
test('returns hljs', done => {
|
||||||
const firstCallHandler = sinon.stub();
|
const firstCallHandler = sinon.stub();
|
||||||
element.get().then(firstCallHandler);
|
element.getHLJS().then(firstCallHandler);
|
||||||
flush(() => {
|
flush(() => {
|
||||||
assert.isTrue(firstCallHandler.called);
|
assert.isTrue(firstCallHandler.called);
|
||||||
assert.isTrue(firstCallHandler.calledWith(hljsStub));
|
assert.isTrue(firstCallHandler.calledWith(hljsStub));
|
||||||
@@ -114,7 +117,7 @@ limitations under the License.
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('configures hljs', done => {
|
test('configures hljs', done => {
|
||||||
element.get().then(() => {
|
element.getHLJS().then(() => {
|
||||||
assert.isTrue(window.hljs.configure.calledOnce);
|
assert.isTrue(window.hljs.configure.calledOnce);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -123,15 +126,10 @@ limitations under the License.
|
|||||||
|
|
||||||
suite('_getHLJSUrl', () => {
|
suite('_getHLJSUrl', () => {
|
||||||
suite('checking _getLibRoot', () => {
|
suite('checking _getLibRoot', () => {
|
||||||
let libRootStub;
|
|
||||||
let root;
|
let root;
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
libRootStub = sinon.stub(element, '_getLibRoot', () => root);
|
sandbox.stub(element, '_getLibRoot', () => root);
|
||||||
});
|
|
||||||
|
|
||||||
teardown(() => {
|
|
||||||
libRootStub.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('with no 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-ranged-comment-layer/gr-ranged-comment-layer_test.html',
|
||||||
'diff/gr-selection-action-box/gr-selection-action-box_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-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-default-editor/gr-default-editor_test.html',
|
||||||
'edit/gr-edit-controls/gr-edit-controls_test.html',
|
'edit/gr-edit-controls/gr-edit-controls_test.html',
|
||||||
'edit/gr-edit-file-controls/gr-edit-file-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-endpoints_test.html',
|
||||||
'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
|
'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
|
||||||
'shared/gr-fixed-panel/gr-fixed-panel_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-limited-text/gr-limited-text_test.html',
|
||||||
'shared/gr-linked-chip/gr-linked-chip_test.html',
|
'shared/gr-linked-chip/gr-linked-chip_test.html',
|
||||||
'shared/gr-linked-text/gr-linked-text_test.html',
|
'shared/gr-linked-text/gr-linked-text_test.html',
|
||||||
|
Reference in New Issue
Block a user