UNPKG

6.27 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
4 * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7 * Code distributed by Google as part of the polymer project is also
8 * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9 */
10
11(function() {
12 'use strict';
13
14 /**
15 * Basic flow of the loader process
16 *
17 * There are 4 flows the loader can take when booting up
18 *
19 * - Synchronous script, no polyfills needed
20 * - wait for `DOMContentLoaded`
21 * - fire WCR event, as there could not be any callbacks passed to `waitFor`
22 *
23 * - Synchronous script, polyfills needed
24 * - document.write the polyfill bundle
25 * - wait on the `load` event of the bundle to batch Custom Element upgrades
26 * - wait for `DOMContentLoaded`
27 * - run callbacks passed to `waitFor`
28 * - fire WCR event
29 *
30 * - Asynchronous script, no polyfills needed
31 * - wait for `DOMContentLoaded`
32 * - run callbacks passed to `waitFor`
33 * - fire WCR event
34 *
35 * - Asynchronous script, polyfills needed
36 * - Append the polyfill bundle script
37 * - wait for `load` event of the bundle
38 * - batch Custom Element Upgrades
39 * - run callbacks pass to `waitFor`
40 * - fire WCR event
41 */
42
43 var polyfillsLoaded = false;
44 var whenLoadedFns = [];
45 var allowUpgrades = false;
46 var flushFn;
47
48 function fireEvent() {
49 window.WebComponents.ready = true;
50 document.dispatchEvent(new CustomEvent('WebComponentsReady', { bubbles: true }));
51 }
52
53 function batchCustomElements() {
54 if (window.customElements && customElements.polyfillWrapFlushCallback) {
55 customElements.polyfillWrapFlushCallback(function (flushCallback) {
56 flushFn = flushCallback;
57 if (allowUpgrades) {
58 flushFn();
59 }
60 });
61 }
62 }
63
64 function asyncReady() {
65 batchCustomElements();
66 ready();
67 }
68
69 function ready() {
70 // bootstrap <template> elements before custom elements
71 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
72 HTMLTemplateElement.bootstrap(window.document);
73 }
74 polyfillsLoaded = true;
75 runWhenLoadedFns().then(fireEvent);
76 }
77
78 function runWhenLoadedFns() {
79 allowUpgrades = false;
80 var fnsMap = whenLoadedFns.map(function(fn) {
81 return fn instanceof Function ? fn() : fn;
82 });
83 whenLoadedFns = [];
84 return Promise.all(fnsMap).then(function() {
85 allowUpgrades = true;
86 flushFn && flushFn();
87 }).catch(function(err) {
88 console.error(err);
89 });
90 }
91
92 window.WebComponents = window.WebComponents || {};
93 window.WebComponents.ready = window.WebComponents.ready || false;
94 window.WebComponents.waitFor = window.WebComponents.waitFor || function(waitFn) {
95 if (!waitFn) {
96 return;
97 }
98 whenLoadedFns.push(waitFn);
99 if (polyfillsLoaded) {
100 runWhenLoadedFns();
101 }
102 };
103 window.WebComponents._batchCustomElements = batchCustomElements;
104
105 var name = 'webcomponents-loader.js';
106 // Feature detect which polyfill needs to be imported.
107 var polyfills = [];
108 if (!('attachShadow' in Element.prototype && 'getRootNode' in Element.prototype) ||
109 (window.ShadyDOM && window.ShadyDOM.force)) {
110 polyfills.push('sd');
111 }
112 if (!window.customElements || window.customElements.forcePolyfill) {
113 polyfills.push('ce');
114 }
115
116 var needsTemplate = (function() {
117 // no real <template> because no `content` property (IE and older browsers)
118 var t = document.createElement('template');
119 if (!('content' in t)) {
120 return true;
121 }
122 // broken doc fragment (older Edge)
123 if (!(t.content.cloneNode() instanceof DocumentFragment)) {
124 return true;
125 }
126 // broken <template> cloning (Edge up to at least version 17)
127 var t2 = document.createElement('template');
128 t2.content.appendChild(document.createElement('div'));
129 t.content.appendChild(t2);
130 var clone = t.cloneNode(true);
131 return (clone.content.childNodes.length === 0 ||
132 clone.content.firstChild.content.childNodes.length === 0);
133 })();
134
135 // NOTE: any browser that does not have template or ES6 features
136 // must load the full suite of polyfills.
137 if (!window.Promise || !Array.from || !window.URL || !window.Symbol || needsTemplate) {
138 polyfills = ['sd-ce-pf'];
139 }
140
141 if (polyfills.length) {
142 var url;
143 var polyfillFile = 'bundles/webcomponents-' + polyfills.join('-') + '.js';
144
145 // Load it from the right place.
146 if (window.WebComponents.root) {
147 url = window.WebComponents.root + polyfillFile;
148 } else {
149 var script = document.querySelector('script[src*="' + name +'"]');
150 // Load it from the right place.
151 url = script.src.replace(name, polyfillFile);
152 }
153
154 var newScript = document.createElement('script');
155 newScript.src = url;
156 // if readyState is 'loading', this script is synchronous
157 if (document.readyState === 'loading') {
158 // make sure custom elements are batched whenever parser gets to the injected script
159 newScript.setAttribute('onload', 'window.WebComponents._batchCustomElements()');
160 document.write(newScript.outerHTML);
161 document.addEventListener('DOMContentLoaded', ready);
162 } else {
163 newScript.addEventListener('load', function () {
164 asyncReady();
165 });
166 newScript.addEventListener('error', function () {
167 throw new Error('Could not load polyfill bundle' + url);
168 });
169 document.head.appendChild(newScript);
170 }
171 } else {
172 // if readyState is 'complete', script is loaded imperatively on a spec-compliant browser, so just fire WCR
173 if (document.readyState === 'complete') {
174 polyfillsLoaded = true;
175 fireEvent();
176 } else {
177 // this script may come between DCL and load, so listen for both, and cancel load listener if DCL fires
178 window.addEventListener('load', ready);
179 window.addEventListener('DOMContentLoaded', function() {
180 window.removeEventListener('load', ready);
181 ready();
182 })
183 }
184 }
185})();