Make bundles with the Custom Elements es5 compatibility shim

Fixes #710

Also, fix loaders to be compatible with ES5-only browsers

Fixes #706
This commit is contained in:
Daniel Freedman
2017-03-06 11:46:12 -08:00
parent 4c5a91b256
commit 20e23374b9
8 changed files with 1370 additions and 9 deletions

View File

@@ -38,7 +38,7 @@ Note that because the bundle will be loaded asynchronously, you should wait for
loaded and are ready to be used (i.e. if you want to dynamically load other custom
elements, etc.). Here's an example:
```
```html
<!-- Load polyfills; note that "loader" will load these async -->
<script src="bower_components/webcomponentsjs/webcomponents-loader.js"></script>
@@ -61,6 +61,12 @@ elements, etc.). Here's an example:
</script>
```
## `webcomponents-es5-loader.js`
Due to the requirement that Custom Elements be ES6 classes (https://html.spec.whatwg.org/multipage/scripting.html#custom-element-conformance), it may make sense to precompile your elements down to ES5 for wider consumption.
However, this will conflict with the presence of native Custom Elements in browsers that support them.
For these scenarios, use the `webcomponents-es5-loader.js` file to use the Custom Elements native compatibility shim where needed.
## Browser Support
The polyfills are intended to work in the latest versions of evergreen browsers. See below

View File

@@ -0,0 +1,20 @@
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';
/*
* Polyfills loaded: Custom Elements ES5 Shim
* Used in: Chrome
*/
import '../bower_components/custom-elements/src/native-shim.js'
import '../src/post-polyfill.js'
import '../src/unresolved.js'

View File

@@ -0,0 +1,21 @@
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';
/*
* Polyfills loaded: HTML Imports + Custom Elements ES5 shim
* Used in: Safari Tech Preview, Chrome
*/
import '../bower_components/custom-elements/src/native-shim.js'
import '../bower_components/html-imports/src/html-imports.js'
import '../src/post-polyfill.js'
import '../src/unresolved.js'

View File

@@ -147,6 +147,14 @@ gulp.task('closurify-sd-ce', () => {
return closurify('webcomponents-sd-ce')
});
gulp.task('debugify-ce-es5', () => {
return debugify('webcomponents-ce-es5');
});
gulp.task('debugify-hi-ce-es5', () => {
return debugify('webcomponents-hi-ce-es5');
})
gulp.task('refresh-bower', () => {
return del('bower_components').then(() => {
let resolve, reject;
@@ -161,8 +169,8 @@ gulp.task('default', (cb) => {
});
gulp.task('clean-builds', () => {
return del(['webcomponents*.js{,.map}', '!webcomponents-loader.js']);
})
return del(['webcomponents*.js{,.map}', '!webcomponents-{es5-,}loader.js']);
});
gulp.task('debug', (cb) => {
const tasks = [
@@ -170,7 +178,9 @@ gulp.task('debug', (cb) => {
'debugify-hi-ce',
'debugify-hi-sd-ce',
'debugify-hi-sd-ce-pf',
'debugify-sd-ce'
'debugify-sd-ce',
'debugify-ce-es5',
'debugify-hi-ce-es5'
];
runseq('clean-builds', tasks, cb);
});
@@ -181,7 +191,9 @@ gulp.task('closure', (cb) => {
'closurify-hi-ce',
'closurify-hi-sd-ce',
'closurify-hi-sd-ce-pf',
'closurify-sd-ce'
'closurify-sd-ce',
'debugify-ce-es5',
'debugify-hi-ce-es5'
];
runseq('clean-builds', ...tasks, cb);
});

267
webcomponents-ce-es5.js Normal file
View File

@@ -0,0 +1,267 @@
(function () {
'use strict';
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
* This shim allows elements written in, or compiled to, ES5 to work on native
* implementations of Custom Elements.
*
* ES5-style classes don't work with native Custom Elements because the
* HTMLElement constructor uses the value of `new.target` to look up the custom
* element definition for the currently called constructor. `new.target` is only
* set when `new` is called and is only propagated via super() calls. super()
* is not emulatable in ES5. The pattern of `SuperClass.call(this)`` only works
* when extending other ES5-style classes, and does not propagate `new.target`.
*
* This shim allows the native HTMLElement constructor to work by generating and
* registering a stand-in class instead of the users custom element class. This
* stand-in class's constructor has an actual call to super().
* `customElements.define()` and `customElements.get()` are both overridden to
* hide this stand-in class from users.
*
* In order to create instance of the user-defined class, rather than the stand
* in, the stand-in's constructor swizzles its instances prototype and invokes
* the user-defined constructor. When the user-defined constructor is called
* directly it creates an instance of the stand-in class to get a real extension
* of HTMLElement and returns that.
*
* There are two important constructors: A patched HTMLElement constructor, and
* the StandInElement constructor. They both will be called to create an element
* but which is called first depends on whether the browser creates the element
* or the user-defined constructor is called directly. The variables
* `browserConstruction` and `userConstruction` control the flow between the
* two constructors.
*
* This shim should be better than forcing the polyfill because:
* 1. It's smaller
* 2. All reaction timings are the same as native (mostly synchronous)
* 3. All reaction triggering DOM operations are automatically supported
*
* There are some restrictions and requirements on ES5 constructors:
* 1. All constructors in a inheritance hierarchy must be ES5-style, so that
* they can be called with Function.call(). This effectively means that the
* whole application must be compiled to ES5.
* 2. Constructors must return the value of the emulated super() call. Like
* `return SuperClass.call(this)`
* 3. The `this` reference should not be used before the emulated super() call
* just like `this` is illegal to use before super() in ES6.
* 4. Constructors should not create other custom elements before the emulated
* super() call. This is the same restriction as with native custom
* elements.
*
* Compiling valid class-based custom elements to ES5 will satisfy these
* requirements with the latest version of popular transpilers.
*/
(() => {
'use strict';
// Do nothing if `customElements` does not exist.
if (!window.customElements) return;
const NativeHTMLElement = window.HTMLElement;
const nativeDefine = window.customElements.define;
const nativeGet = window.customElements.get;
/**
* Map of user-provided constructors to tag names.
*
* @type {Map<Function, string>}
*/
const tagnameByConstructor = new Map();
/**
* Map of tag names to user-provided constructors.
*
* @type {Map<string, Function>}
*/
const constructorByTagname = new Map();
/**
* Whether the constructors are being called by a browser process, ie parsing
* or createElement.
*/
let browserConstruction = false;
/**
* Whether the constructors are being called by a user-space process, ie
* calling an element constructor.
*/
let userConstruction = false;
window.HTMLElement = function() {
if (!browserConstruction) {
const tagname = tagnameByConstructor.get(this.constructor);
const fakeClass = nativeGet.call(window.customElements, tagname);
// Make sure that the fake constructor doesn't call back to this constructor
userConstruction = true;
const instance = new (fakeClass)();
return instance;
}
// Else do nothing. This will be reached by ES5-style classes doing
// HTMLElement.call() during initialization
browserConstruction = false;
};
// By setting the patched HTMLElement's prototype property to the native
// HTMLElement's prototype we make sure that:
// document.createElement('a') instanceof HTMLElement
// works because instanceof uses HTMLElement.prototype, which is on the
// ptototype chain of built-in elements.
window.HTMLElement.prototype = NativeHTMLElement.prototype;
window.customElements.define = (tagname, elementClass) => {
const elementProto = elementClass.prototype;
const StandInElement = class extends NativeHTMLElement {
constructor() {
// Call the native HTMLElement constructor, this gives us the
// under-construction instance as `this`:
super();
// The prototype will be wrong up because the browser used our fake
// class, so fix it:
Object.setPrototypeOf(this, elementProto);
if (!userConstruction) {
// Make sure that user-defined constructor bottom's out to a do-nothing
// HTMLElement() call
browserConstruction = true;
// Call the user-defined constructor on our instance:
elementClass.call(this);
}
userConstruction = false;
}
};
const standInProto = StandInElement.prototype;
StandInElement.observedAttributes = elementClass.observedAttributes;
standInProto.connectedCallback = elementProto.connectedCallback;
standInProto.disconnectedCallback = elementProto.disconnectedCallback;
standInProto.attributeChangedCallback = elementProto.attributeChangedCallback;
standInProto.adoptedCallback = elementProto.adoptedCallback;
tagnameByConstructor.set(elementClass, tagname);
constructorByTagname.set(tagname, elementClass);
nativeDefine.call(window.customElements, tagname, StandInElement);
};
window.customElements.get = (tagname) => constructorByTagname.get(tagname);
})();
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function() {
'use strict';
var customElements = window['customElements'];
var HTMLImports = window['HTMLImports'];
if (customElements && customElements['polyfillWrapFlushCallback']) {
// Here we ensure that the public `HTMLImports.whenReady`
// always comes *after* custom elements have upgraded.
var flushCallback;
var runAndClearCallback = function runAndClearCallback() {
if (flushCallback) {
var cb = flushCallback;
flushCallback = null;
cb();
return true;
}
};
var origWhenReady = HTMLImports['whenReady'];
customElements['polyfillWrapFlushCallback'](function(cb) {
flushCallback = cb;
origWhenReady(runAndClearCallback);
});
HTMLImports['whenReady'] = function(cb) {
origWhenReady(function() {
// custom element code may add dynamic imports
// to match processing of native custom elements before
// domContentLoaded, we wait for these imports to resolve first.
if (runAndClearCallback()) {
HTMLImports['whenReady'](cb);
} else {
cb();
}
});
};
}
HTMLImports['whenReady'](function() {
requestAnimationFrame(function() {
window.dispatchEvent(new CustomEvent('WebComponentsReady'));
});
});
})();
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function() {
'use strict';
// It's desireable to provide a default stylesheet
// that's convenient for styling unresolved elements, but
// it's cumbersome to have to include this manually in every page.
// It would make sense to put inside some HTMLImport but
// the HTMLImports polyfill does not allow loading of stylesheets
// that block rendering. Therefore this injection is tolerated here.
//
// NOTE: position: relative fixes IE's failure to inherit opacity
// when a child is not statically positioned.
var style = document.createElement('style');
style.textContent = ''
+ 'body {'
+ 'transition: opacity ease-in 0.2s;'
+ ' } \n'
+ 'body[unresolved] {'
+ 'opacity: 0; display: block; overflow: hidden; position: relative;'
+ ' } \n'
;
var head = document.querySelector('head');
head.insertBefore(style, head.firstChild);
})();
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/*
* Polyfills loaded: Custom Elements ES5 Shim
* Used in: Chrome
*/
}());

View File

@@ -0,0 +1,51 @@
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function() {
'use strict';
// Feature detect which polyfill needs to be imported.
var polyfills = [];
if (!('import' in document.createElement('link'))) {
polyfills.push('hi');
}
if (!('attachShadow' in Element.prototype && 'getRootNode' in Element.prototype) ||
(window.ShadyDOM && window.ShadyDOM.force)) {
polyfills.push('sd');
}
if (!window.customElements || window.customElements.forcePolyfill) {
polyfills.push('ce');
} else {
polyfills.push('ce-es5');
}
if (!('content' in document.createElement('template')) || !window.Promise ||
// Edge has broken fragment cloning which means you cannot clone template.content
!(document.createDocumentFragment().cloneNode() instanceof DocumentFragment)) {
polyfills.push('pf');
}
if (polyfills.length === 4) { // hi-ce-sd-pf is actually called lite.
polyfills = ['lite'];
}
if (polyfills.length) {
var script = document.querySelector('script[src*="webcomponents-loader.js"]');
var newScript = document.createElement('script');
// Load it from the right place.
var replacement = 'webcomponents-' + polyfills.join('-') + '.js';
var url = script.src.replace('webcomponents-loader.js', replacement);
newScript.src = url;
document.head.appendChild(newScript);
} else {
// Ensure `WebComponentsReady` is fired also when there are no polyfills loaded.
requestAnimationFrame(function() {
window.dispatchEvent(new CustomEvent('WebComponentsReady'));
});
}
})();

983
webcomponents-hi-ce-es5.js Normal file
View File

@@ -0,0 +1,983 @@
(function () {
'use strict';
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/**
* This shim allows elements written in, or compiled to, ES5 to work on native
* implementations of Custom Elements.
*
* ES5-style classes don't work with native Custom Elements because the
* HTMLElement constructor uses the value of `new.target` to look up the custom
* element definition for the currently called constructor. `new.target` is only
* set when `new` is called and is only propagated via super() calls. super()
* is not emulatable in ES5. The pattern of `SuperClass.call(this)`` only works
* when extending other ES5-style classes, and does not propagate `new.target`.
*
* This shim allows the native HTMLElement constructor to work by generating and
* registering a stand-in class instead of the users custom element class. This
* stand-in class's constructor has an actual call to super().
* `customElements.define()` and `customElements.get()` are both overridden to
* hide this stand-in class from users.
*
* In order to create instance of the user-defined class, rather than the stand
* in, the stand-in's constructor swizzles its instances prototype and invokes
* the user-defined constructor. When the user-defined constructor is called
* directly it creates an instance of the stand-in class to get a real extension
* of HTMLElement and returns that.
*
* There are two important constructors: A patched HTMLElement constructor, and
* the StandInElement constructor. They both will be called to create an element
* but which is called first depends on whether the browser creates the element
* or the user-defined constructor is called directly. The variables
* `browserConstruction` and `userConstruction` control the flow between the
* two constructors.
*
* This shim should be better than forcing the polyfill because:
* 1. It's smaller
* 2. All reaction timings are the same as native (mostly synchronous)
* 3. All reaction triggering DOM operations are automatically supported
*
* There are some restrictions and requirements on ES5 constructors:
* 1. All constructors in a inheritance hierarchy must be ES5-style, so that
* they can be called with Function.call(). This effectively means that the
* whole application must be compiled to ES5.
* 2. Constructors must return the value of the emulated super() call. Like
* `return SuperClass.call(this)`
* 3. The `this` reference should not be used before the emulated super() call
* just like `this` is illegal to use before super() in ES6.
* 4. Constructors should not create other custom elements before the emulated
* super() call. This is the same restriction as with native custom
* elements.
*
* Compiling valid class-based custom elements to ES5 will satisfy these
* requirements with the latest version of popular transpilers.
*/
(() => {
'use strict';
// Do nothing if `customElements` does not exist.
if (!window.customElements) return;
const NativeHTMLElement = window.HTMLElement;
const nativeDefine = window.customElements.define;
const nativeGet = window.customElements.get;
/**
* Map of user-provided constructors to tag names.
*
* @type {Map<Function, string>}
*/
const tagnameByConstructor = new Map();
/**
* Map of tag names to user-provided constructors.
*
* @type {Map<string, Function>}
*/
const constructorByTagname = new Map();
/**
* Whether the constructors are being called by a browser process, ie parsing
* or createElement.
*/
let browserConstruction = false;
/**
* Whether the constructors are being called by a user-space process, ie
* calling an element constructor.
*/
let userConstruction = false;
window.HTMLElement = function() {
if (!browserConstruction) {
const tagname = tagnameByConstructor.get(this.constructor);
const fakeClass = nativeGet.call(window.customElements, tagname);
// Make sure that the fake constructor doesn't call back to this constructor
userConstruction = true;
const instance = new (fakeClass)();
return instance;
}
// Else do nothing. This will be reached by ES5-style classes doing
// HTMLElement.call() during initialization
browserConstruction = false;
};
// By setting the patched HTMLElement's prototype property to the native
// HTMLElement's prototype we make sure that:
// document.createElement('a') instanceof HTMLElement
// works because instanceof uses HTMLElement.prototype, which is on the
// ptototype chain of built-in elements.
window.HTMLElement.prototype = NativeHTMLElement.prototype;
window.customElements.define = (tagname, elementClass) => {
const elementProto = elementClass.prototype;
const StandInElement = class extends NativeHTMLElement {
constructor() {
// Call the native HTMLElement constructor, this gives us the
// under-construction instance as `this`:
super();
// The prototype will be wrong up because the browser used our fake
// class, so fix it:
Object.setPrototypeOf(this, elementProto);
if (!userConstruction) {
// Make sure that user-defined constructor bottom's out to a do-nothing
// HTMLElement() call
browserConstruction = true;
// Call the user-defined constructor on our instance:
elementClass.call(this);
}
userConstruction = false;
}
};
const standInProto = StandInElement.prototype;
StandInElement.observedAttributes = elementClass.observedAttributes;
standInProto.connectedCallback = elementProto.connectedCallback;
standInProto.disconnectedCallback = elementProto.disconnectedCallback;
standInProto.attributeChangedCallback = elementProto.attributeChangedCallback;
standInProto.adoptedCallback = elementProto.adoptedCallback;
tagnameByConstructor.set(elementClass, tagname);
constructorByTagname.set(tagname, elementClass);
nativeDefine.call(window.customElements, tagname, StandInElement);
};
window.customElements.get = (tagname) => constructorByTagname.get(tagname);
})();
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(scope => {
/********************* base setup *********************/
const useNative = Boolean('import' in document.createElement('link'));
// Polyfill `currentScript` for browsers without it.
let currentScript = null;
if ('currentScript' in document === false) {
Object.defineProperty(document, 'currentScript', {
get() {
return currentScript ||
// NOTE: only works when called in synchronously executing code.
// readyState should check if `loading` but IE10 is
// interactive when scripts run so we cheat. This is not needed by
// html-imports polyfill but helps generally polyfill `currentScript`.
(document.readyState !== 'complete' ?
document.scripts[document.scripts.length - 1] : null);
},
configurable: true
});
}
/********************* path fixup *********************/
const ABS_URL_TEST = /(^\/)|(^#)|(^[\w-\d]*:)/;
const CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
const CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
const STYLESHEET_REGEXP = /(<link[^>]*)(rel=['|"]?stylesheet['|"]?[^>]*>)/g;
// path fixup: style elements in imports must be made relative to the main
// document. We fixup url's in url() and @import.
const Path = {
fixUrls(element, base) {
if (element.href) {
element.setAttribute('href',
Path.replaceAttrUrl(element.getAttribute('href'), base));
}
if (element.src) {
element.setAttribute('src',
Path.replaceAttrUrl(element.getAttribute('src'), base));
}
if (element.localName === 'style') {
const r = Path.replaceUrls(element.textContent, base, CSS_URL_REGEXP);
element.textContent = Path.replaceUrls(r, base, CSS_IMPORT_REGEXP);
}
},
replaceUrls(text, linkUrl, regexp) {
return text.replace(regexp, (m, pre, url, post) => {
let urlPath = url.replace(/["']/g, '');
if (linkUrl) {
urlPath = Path.resolveUrl(urlPath, linkUrl);
}
return pre + '\'' + urlPath + '\'' + post;
});
},
replaceAttrUrl(text, linkUrl) {
if (text && ABS_URL_TEST.test(text)) {
return text;
} else {
return Path.resolveUrl(text, linkUrl);
}
},
resolveUrl(url, base) {
// Lazy feature detection.
if (Path.__workingURL === undefined) {
Path.__workingURL = false;
try {
const u = new URL('b', 'http://a');
u.pathname = 'c%20d';
Path.__workingURL = (u.href === 'http://a/c%20d');
} catch (e) {}
}
if (Path.__workingURL) {
return (new URL(url, base)).href;
}
// Fallback to creating an anchor into a disconnected document.
let doc = Path.__tempDoc;
if (!doc) {
doc = document.implementation.createHTMLDocument('temp');
Path.__tempDoc = doc;
doc.__base = doc.createElement('base');
doc.head.appendChild(doc.__base);
doc.__anchor = doc.createElement('a');
}
doc.__base.href = base;
doc.__anchor.href = url;
return doc.__anchor.href || url;
}
};
/********************* Xhr processor *********************/
const Xhr = {
async: true,
/**
* @param {!string} url
* @param {!function(!string, string=)} success
* @param {!function(!string)} fail
*/
load(url, success, fail) {
if (!url) {
fail('error: href must be specified');
} else if (url.match(/^data:/)) {
// Handle Data URI Scheme
const pieces = url.split(',');
const header = pieces[0];
let resource = pieces[1];
if (header.indexOf(';base64') > -1) {
resource = atob(resource);
} else {
resource = decodeURIComponent(resource);
}
success(resource);
} else {
const request = new XMLHttpRequest();
request.open('GET', url, Xhr.async);
request.onload = () => {
// Servers redirecting an import can add a Location header to help us
// polyfill correctly. Handle relative and full paths.
let redirectedUrl = request.getResponseHeader('Location');
if (redirectedUrl && redirectedUrl.indexOf('/') === 0) {
// In IE location.origin might not work
// https://connect.microsoft.com/IE/feedback/details/1763802/location-origin-is-undefined-in-ie-11-on-windows-10-but-works-on-windows-7
const origin = (location.origin || location.protocol + '//' + location.host);
redirectedUrl = origin + redirectedUrl;
}
const resource = /** @type {string} */ (request.response || request.responseText);
if (request.status === 304 || request.status === 0 ||
request.status >= 200 && request.status < 300) {
success(resource, redirectedUrl);
} else {
fail(resource);
}
};
request.send();
}
}
};
/********************* importer *********************/
const isIE = /Trident/.test(navigator.userAgent) ||
/Edge\/\d./i.test(navigator.userAgent);
const importSelector = 'link[rel=import]';
// Used to disable loading of resources.
const importDisableType = 'import-disable';
const disabledLinkSelector = `link[rel=stylesheet][href][type=${importDisableType}]`;
const importDependenciesSelector = `${importSelector}, ${disabledLinkSelector},
style:not([type]), link[rel=stylesheet][href]:not([type]),
script:not([type]), script[type="application/javascript"],
script[type="text/javascript"]`;
const importDependencyAttr = 'import-dependency';
const rootImportSelector = `${importSelector}:not(${importDependencyAttr})`;
const pendingScriptsSelector = `script[${importDependencyAttr}]`;
const pendingStylesSelector = `style[${importDependencyAttr}],
link[rel=stylesheet][${importDependencyAttr}]`;
/**
* Importer will:
* - load any linked import documents (with deduping)
* - whenever an import is loaded, prompt the parser to try to parse
* - observe imported documents for new elements (these are handled via the
* dynamic importer)
*/
class Importer {
constructor() {
this.documents = {};
// Used to keep track of pending loads, so that flattening and firing of
// events can be done when all resources are ready.
this.inflight = 0;
this.dynamicImportsMO = new MutationObserver(m => this.handleMutations(m));
// 1. Load imports contents
// 2. Assign them to first import links on the document
// 3. Wait for import styles & scripts to be done loading/running
// 4. Fire load/error events
whenDocumentReady(() => {
// Observe changes on <head>.
this.dynamicImportsMO.observe(document.head, {
childList: true,
subtree: true
});
this.loadImports(document);
});
}
/**
* @param {!(HTMLDocument|DocumentFragment|Element)} doc
*/
loadImports(doc) {
const links = /** @type {!NodeList<!HTMLLinkElement>} */
(doc.querySelectorAll(importSelector));
for (let i = 0, l = links.length; i < l; i++) {
this.loadImport(links[i]);
}
}
/**
* @param {!HTMLLinkElement} link
*/
loadImport(link) {
const url = link.href;
// This resource is already being handled by another import.
if (this.documents[url] !== undefined) {
// If import is already loaded, we can safely associate it to the link
// and fire the load/error event.
const imp = this.documents[url];
if (imp && imp['__loaded']) {
link.import = imp;
this.fireEventIfNeeded(link);
}
return;
}
this.inflight++;
// Mark it as pending to notify others this url is being loaded.
this.documents[url] = 'pending';
Xhr.load(url, (resource, redirectedUrl) => {
const doc = this.makeDocument(resource, redirectedUrl || url);
this.documents[url] = doc;
this.inflight--;
// Load subtree.
this.loadImports(doc);
this.processImportsIfLoadingDone();
}, () => {
// If load fails, handle error.
this.documents[url] = null;
this.inflight--;
this.processImportsIfLoadingDone();
});
}
/**
* Creates a new document containing resource and normalizes urls accordingly.
* @param {string=} resource
* @param {string=} url
* @return {!DocumentFragment}
*/
makeDocument(resource, url) {
if (!resource) {
return document.createDocumentFragment();
}
if (isIE) {
// <link rel=stylesheet> should be appended to <head>. Not doing so
// in IE/Edge breaks the cascading order. We disable the loading by
// setting the type before setting innerHTML to avoid loading
// resources twice.
resource = resource.replace(STYLESHEET_REGEXP, (match, p1, p2) => {
if (match.indexOf('type=') === -1) {
return `${p1} type=${importDisableType} ${p2}`;
}
return match;
});
}
let content;
const template = /** @type {!HTMLTemplateElement} */
(document.createElement('template'));
template.innerHTML = resource;
if (template.content) {
// This creates issues in Safari10 when used with shadydom (see #12).
content = template.content;
} else {
// <template> not supported, create fragment and move content into it.
content = document.createDocumentFragment();
while (template.firstChild) {
content.appendChild(template.firstChild);
}
}
// Support <base> in imported docs. Resolve url and remove its href.
const baseEl = content.querySelector('base');
if (baseEl) {
url = Path.replaceAttrUrl(baseEl.getAttribute('href'), url);
baseEl.removeAttribute('href');
}
const n$ = /** @type {!NodeList<!(HTMLLinkElement|HTMLScriptElement|HTMLStyleElement)>} */
(content.querySelectorAll(importDependenciesSelector));
// For source map hints.
let inlineScriptIndex = 0;
for (let i = 0, l = n$.length, n; i < l && (n = n$[i]); i++) {
// Listen for load/error events, then fix urls.
whenElementLoaded(n);
Path.fixUrls(n, url);
// Mark for easier selectors.
n.setAttribute(importDependencyAttr, '');
// Generate source map hints for inline scripts.
if (n.localName === 'script' && !n.src && n.textContent) {
const num = inlineScriptIndex ? `-${inlineScriptIndex}` : '';
const content = n.textContent + `\n//# sourceURL=${url}${num}.js\n`;
// We use the src attribute so it triggers load/error events, and it's
// easier to capture errors (e.g. parsing) like this.
n.setAttribute('src', 'data:text/javascript;charset=utf-8,' + encodeURIComponent(content));
n.textContent = '';
inlineScriptIndex++;
}
}
return content;
}
/**
* Waits for loaded imports to finish loading scripts and styles, then fires
* the load/error events.
*/
processImportsIfLoadingDone() {
// Wait until all resources are ready, then load import resources.
if (this.inflight) {
return;
}
// Stop observing, flatten & load resource, then restart observing <head>.
this.dynamicImportsMO.disconnect();
this.flatten(document);
// We wait for styles to load, and at the same time we execute the scripts,
// then fire the load/error events for imports to have faster whenReady
// callback execution.
// NOTE: This is different for native behavior where scripts would be
// executed after the styles before them are loaded.
// To achieve that, we could select pending styles and scripts in the
// document and execute them sequentially in their dom order.
let scriptsOk = false,
stylesOk = false;
const onLoadingDone = () => {
if (stylesOk && scriptsOk) {
// Restart observing.
this.dynamicImportsMO.observe(document.head, {
childList: true,
subtree: true
});
this.fireEvents();
}
};
this.waitForStyles(() => {
stylesOk = true;
onLoadingDone();
});
this.runScripts(() => {
scriptsOk = true;
onLoadingDone();
});
}
/**
* @param {!HTMLDocument} doc
*/
flatten(doc) {
const n$ = /** @type {!NodeList<!HTMLLinkElement>} */
(doc.querySelectorAll(importSelector));
for (let i = 0, l = n$.length, n; i < l && (n = n$[i]); i++) {
const imp = this.documents[n.href];
n.import = /** @type {!Document} */ (imp);
if (imp && imp.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// We set the .import to be the link itself, and update its readyState.
// Other links with the same href will point to this link.
this.documents[n.href] = n;
n.readyState = 'loading';
// Suppress Closure warning about incompatible subtype assignment.
( /** @type {!HTMLElement} */ (n).import = n);
// Override baseURI so that link.import.baseURI can be used seemlessly
// on native or polyfilled html-imports.
Object.defineProperty(n, 'baseURI', {
get: () => n.href,
configurable: true,
enumerable: true
});
this.flatten(imp);
n.appendChild(imp);
}
}
}
/**
* Replaces all the imported scripts with a clone in order to execute them.
* Updates the `currentScript`.
* @param {!function()} callback
*/
runScripts(callback) {
const s$ = document.querySelectorAll(pendingScriptsSelector);
const l = s$.length;
const cloneScript = i => {
if (i < l) {
// The pending scripts have been generated through innerHTML and
// browsers won't execute them for security reasons. We cannot use
// s.cloneNode(true) either, the only way to run the script is manually
// creating a new element and copying its attributes.
const s = s$[i];
const clone = /** @type {!HTMLScriptElement} */
(document.createElement('script'));
// Remove import-dependency attribute to avoid double cloning.
s.removeAttribute(importDependencyAttr);
for (let j = 0, ll = s.attributes.length; j < ll; j++) {
clone.setAttribute(s.attributes[j].name, s.attributes[j].value);
}
// Update currentScript and replace original with clone script.
currentScript = clone;
s.parentNode.replaceChild(clone, s);
whenElementLoaded(clone, () => {
currentScript = null;
cloneScript(i + 1);
});
} else {
callback();
}
};
cloneScript(0);
}
/**
* Waits for all the imported stylesheets/styles to be loaded.
* @param {!function()} callback
*/
waitForStyles(callback) {
const s$ = /** @type {!NodeList<!(HTMLLinkElement|HTMLStyleElement)>} */
(document.querySelectorAll(pendingStylesSelector));
let pending = s$.length;
if (!pending) {
callback();
return;
}
// <link rel=stylesheet> should be appended to <head>. Not doing so
// in IE/Edge breaks the cascading order
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10472273/
// If there is one <link rel=stylesheet> imported, we must move all imported
// links and styles to <head>.
const needsMove = isIE && !!document.querySelector(disabledLinkSelector);
for (let i = 0, l = s$.length, s; i < l && (s = s$[i]); i++) {
// Listen for load/error events, remove selector once is done loading.
whenElementLoaded(s, () => {
s.removeAttribute(importDependencyAttr);
if (--pending === 0) {
callback();
}
});
// Check if was already moved to head, to handle the case where the element
// has already been moved but it is still loading.
if (needsMove && s.parentNode !== document.head) {
// Replace the element we're about to move with a placeholder.
const placeholder = document.createElement(s.localName);
// Add reference of the moved element.
placeholder['__appliedElement'] = s;
// Disable this from appearing in document.styleSheets.
placeholder.setAttribute('type', 'import-placeholder');
// Append placeholder next to the sibling, and move original to <head>.
s.parentNode.insertBefore(placeholder, s.nextSibling);
let newSibling = importForElement(s);
while (newSibling && importForElement(newSibling)) {
newSibling = importForElement(newSibling);
}
if (newSibling.parentNode !== document.head) {
newSibling = null;
}
document.head.insertBefore(s, newSibling);
// Enable the loading of <link rel=stylesheet>.
s.removeAttribute('type');
}
}
}
/**
* Fires load/error events for imports in the right order .
*/
fireEvents() {
const n$ = /** @type {!NodeList<!HTMLLinkElement>} */
(document.querySelectorAll(importSelector));
// Inverse order to have events firing bottom-up.
for (let i = n$.length - 1, n; i >= 0 && (n = n$[i]); i--) {
this.fireEventIfNeeded(n);
}
}
/**
* Fires load/error event for the import if this wasn't done already.
* @param {!HTMLLinkElement} link
*/
fireEventIfNeeded(link) {
// Don't fire twice same event.
if (!link['__loaded']) {
link['__loaded'] = true;
// Update link's import readyState.
link.import && (link.import.readyState = 'complete');
const eventType = link.import ? 'load' : 'error';
link.dispatchEvent(newCustomEvent(eventType, {
bubbles: false,
cancelable: false,
detail: undefined
}));
}
}
/**
* @param {Array<MutationRecord>} mutations
*/
handleMutations(mutations) {
for (let i = 0; i < mutations.length; i++) {
const m = mutations[i];
if (!m.addedNodes) {
continue;
}
for (let ii = 0; ii < m.addedNodes.length; ii++) {
const elem = m.addedNodes[ii];
if (!elem || elem.nodeType !== Node.ELEMENT_NODE) {
continue;
}
// NOTE: added scripts are not updating currentScript in IE.
if (isImportLink(elem)) {
this.loadImport( /** @type {!HTMLLinkElement} */ (elem));
} else {
this.loadImports( /** @type {!Element} */ (elem));
}
}
}
}
}
/**
* @param {!Node} node
* @return {boolean}
*/
const isImportLink = node => {
return node.nodeType === Node.ELEMENT_NODE && node.localName === 'link' &&
( /** @type {!HTMLLinkElement} */ (node).rel === 'import');
};
/**
* Waits for an element to finish loading. If already done loading, it will
* mark the element accordingly.
* @param {!(HTMLLinkElement|HTMLScriptElement|HTMLStyleElement)} element
* @param {function()=} callback
*/
const whenElementLoaded = (element, callback) => {
if (element['__loaded']) {
callback && callback();
} else if (element.localName === 'script' && !element.src) {
// Inline scripts don't trigger load/error events, consider them already loaded.
element['__loaded'] = true;
callback && callback();
} else {
const onLoadingDone = event => {
element.removeEventListener(event.type, onLoadingDone);
element['__loaded'] = true;
callback && callback();
};
element.addEventListener('load', onLoadingDone);
// NOTE: We listen only for load events in IE/Edge, because in IE/Edge
// <style> with @import will fire error events for each failing @import,
// and finally will trigger the load event when all @import are
// finished (even if all fail).
if (!isIE || element.localName !== 'style') {
element.addEventListener('error', onLoadingDone);
}
}
};
/**
* Calls the callback when all imports in the document at call time
* (or at least document ready) have loaded. Callback is called synchronously
* if imports are already done loading.
* @param {function()=} callback
*/
const whenReady = callback => {
// 1. ensure the document is in a ready state (has dom), then
// 2. watch for loading of imports and call callback when done
whenDocumentReady(() => whenImportsReady(() => callback && callback()));
};
/**
* Invokes the callback when document is in ready state. Callback is called
* synchronously if document is already done loading.
* @param {!function()} callback
*/
const whenDocumentReady = callback => {
if (document.readyState !== 'loading') {
callback();
} else {
const stateChanged = () => {
if (document.readyState !== 'loading') {
document.removeEventListener('readystatechange', stateChanged);
callback();
}
};
document.addEventListener('readystatechange', stateChanged);
}
};
/**
* Invokes the callback after all imports are loaded. Callback is called
* synchronously if imports are already done loading.
* @param {!function()} callback
*/
const whenImportsReady = callback => {
let imports = /** @type {!NodeList<!HTMLLinkElement>} */
(document.querySelectorAll(rootImportSelector));
let pending = imports.length;
if (!pending) {
callback();
return;
}
for (let i = 0, l = imports.length, imp; i < l && (imp = imports[i]); i++) {
whenElementLoaded(imp, () => {
if (--pending === 0) {
callback();
}
});
}
};
/**
* Returns the import document containing the element.
* @param {!Node} element
* @return {HTMLLinkElement|Document|undefined}
*/
const importForElement = element => {
if (useNative) {
// Return only if not in the main doc!
return element.ownerDocument !== document ? element.ownerDocument : null;
}
let doc = element['__importDoc'];
if (!doc && element.parentNode) {
doc = /** @type {!Element} */ (element.parentNode);
if (typeof doc.closest === 'function') {
// Element.closest returns the element itself if it matches the selector,
// so we search the closest import starting from the parent.
doc = doc.closest(importSelector);
} else {
// Walk up the parent tree until we find an import.
while (!isImportLink(doc) && (doc = doc.parentNode)) {}
}
element['__importDoc'] = doc;
}
return doc;
};
const newCustomEvent = (type, params) => {
if (typeof window.CustomEvent === 'function') {
return new CustomEvent(type, params);
}
const event = /** @type {!CustomEvent} */ (document.createEvent('CustomEvent'));
event.initCustomEvent(type, Boolean(params.bubbles), Boolean(params.cancelable), params.detail);
return event;
};
if (useNative) {
// Check for imports that might already be done loading by the time this
// script is actually executed. Native imports are blocking, so the ones
// available in the document by this time should already have failed
// or have .import defined.
const imps = /** @type {!NodeList<!HTMLLinkElement>} */
(document.querySelectorAll(importSelector));
for (let i = 0, l = imps.length, imp; i < l && (imp = imps[i]); i++) {
if (!imp.import || imp.import.readyState !== 'loading') {
imp['__loaded'] = true;
}
}
// Listen for load/error events to capture dynamically added scripts.
/**
* @type {!function(!Event)}
*/
const onLoadingDone = event => {
const elem = /** @type {!Element} */ (event.target);
if (isImportLink(elem)) {
elem['__loaded'] = true;
}
};
document.addEventListener('load', onLoadingDone, true /* useCapture */ );
document.addEventListener('error', onLoadingDone, true /* useCapture */ );
} else {
new Importer();
}
/**
Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady`
method. This api is necessary because unlike the native implementation,
script elements do not force imports to resolve. Instead, users should wrap
code in either an `HTMLImportsLoaded` handler or after load time in an
`HTMLImports.whenReady(callback)` call.
NOTE: This module also supports these apis under the native implementation.
Therefore, if this file is loaded, the same code can be used under both
the polyfill and native implementation.
*/
whenReady(() => document.dispatchEvent(newCustomEvent('HTMLImportsLoaded', {
cancelable: true,
bubbles: true,
detail: undefined
})));
// exports
scope.useNative = useNative;
scope.whenReady = whenReady;
scope.importForElement = importForElement;
})(window.HTMLImports = (window.HTMLImports || {}));
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function() {
'use strict';
var customElements = window['customElements'];
var HTMLImports = window['HTMLImports'];
if (customElements && customElements['polyfillWrapFlushCallback']) {
// Here we ensure that the public `HTMLImports.whenReady`
// always comes *after* custom elements have upgraded.
var flushCallback;
var runAndClearCallback = function runAndClearCallback() {
if (flushCallback) {
var cb = flushCallback;
flushCallback = null;
cb();
return true;
}
};
var origWhenReady = HTMLImports['whenReady'];
customElements['polyfillWrapFlushCallback'](function(cb) {
flushCallback = cb;
origWhenReady(runAndClearCallback);
});
HTMLImports['whenReady'] = function(cb) {
origWhenReady(function() {
// custom element code may add dynamic imports
// to match processing of native custom elements before
// domContentLoaded, we wait for these imports to resolve first.
if (runAndClearCallback()) {
HTMLImports['whenReady'](cb);
} else {
cb();
}
});
};
}
HTMLImports['whenReady'](function() {
requestAnimationFrame(function() {
window.dispatchEvent(new CustomEvent('WebComponentsReady'));
});
});
})();
/**
* @license
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function() {
'use strict';
// It's desireable to provide a default stylesheet
// that's convenient for styling unresolved elements, but
// it's cumbersome to have to include this manually in every page.
// It would make sense to put inside some HTMLImport but
// the HTMLImports polyfill does not allow loading of stylesheets
// that block rendering. Therefore this injection is tolerated here.
//
// NOTE: position: relative fixes IE's failure to inherit opacity
// when a child is not statically positioned.
var style = document.createElement('style');
style.textContent = ''
+ 'body {'
+ 'transition: opacity ease-in 0.2s;'
+ ' } \n'
+ 'body[unresolved] {'
+ 'opacity: 0; display: block; overflow: hidden; position: relative;'
+ ' } \n'
;
var head = document.querySelector('head');
head.insertBefore(style, head.firstChild);
})();
/**
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
/*
* Polyfills loaded: HTML Imports + Custom Elements ES5 shim
* Used in: Safari Tech Preview, Chrome
*/
}());

View File

@@ -9,8 +9,9 @@
*/
(function() {
'use strict';
// Feature detect which polyfill needs to be imported.
let polyfills = [];
var polyfills = [];
if (!('import' in document.createElement('link'))) {
polyfills.push('hi');
}
@@ -33,10 +34,10 @@
if (polyfills.length) {
var script = document.querySelector('script[src*="webcomponents-loader.js"]');
let newScript = document.createElement('script');
var newScript = document.createElement('script');
// Load it from the right place.
var url = script.src.replace(
'webcomponents-loader.js', `webcomponents-${polyfills.join('-')}.js`);
var replacement = 'webcomponents-' + polyfills.join('-') + '.js';
var url = script.src.replace('webcomponents-loader.js', replacement);
newScript.src = url;
document.head.appendChild(newScript);
} else {