UNPKG

7.77 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(
51 new CustomEvent('WebComponentsReady', {bubbles: true})
52 );
53 }
54
55 function batchCustomElements() {
56 if (window.customElements && customElements.polyfillWrapFlushCallback) {
57 customElements.polyfillWrapFlushCallback(function (flushCallback) {
58 flushFn = flushCallback;
59 if (allowUpgrades) {
60 flushFn();
61 }
62 });
63 }
64 }
65
66 function asyncReady() {
67 batchCustomElements();
68 ready();
69 }
70
71 function ready() {
72 // bootstrap <template> elements before custom elements
73 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
74 HTMLTemplateElement.bootstrap(window.document);
75 }
76 polyfillsLoaded = true;
77 runWhenLoadedFns().then(fireEvent);
78 }
79
80 function runWhenLoadedFns() {
81 allowUpgrades = false;
82 var fnsMap = whenLoadedFns.map(function (fn) {
83 return fn instanceof Function ? fn() : fn;
84 });
85 whenLoadedFns = [];
86 return Promise.all(fnsMap)
87 .then(function () {
88 allowUpgrades = true;
89 flushFn && flushFn();
90 })
91 .catch(function (err) {
92 console.error(err);
93 });
94 }
95
96 window.WebComponents = window.WebComponents || {};
97 window.WebComponents.ready = window.WebComponents.ready || false;
98 window.WebComponents.waitFor =
99 window.WebComponents.waitFor ||
100 function (waitFn) {
101 if (!waitFn) {
102 return;
103 }
104 whenLoadedFns.push(waitFn);
105 if (polyfillsLoaded) {
106 runWhenLoadedFns();
107 }
108 };
109 window.WebComponents._batchCustomElements = batchCustomElements;
110
111 var name = 'webcomponents-loader.js';
112 // Feature detect which polyfill needs to be imported.
113 var polyfills = [];
114 if (
115 !(
116 'attachShadow' in Element.prototype && 'getRootNode' in Element.prototype
117 ) ||
118 (window.ShadyDOM && window.ShadyDOM.force)
119 ) {
120 polyfills.push('sd');
121 }
122 if (!window.customElements || window.customElements.forcePolyfill) {
123 polyfills.push('ce');
124 }
125
126 var needsTemplate = (function () {
127 // no real <template> because no `content` property (IE and older browsers)
128 var t = document.createElement('template');
129 if (!('content' in t)) {
130 return true;
131 }
132 // broken doc fragment (older Edge)
133 if (!(t.content.cloneNode() instanceof DocumentFragment)) {
134 return true;
135 }
136 // broken <template> cloning (Edge up to at least version 17)
137 var t2 = document.createElement('template');
138 t2.content.appendChild(document.createElement('div'));
139 t.content.appendChild(t2);
140 var clone = t.cloneNode(true);
141 return (
142 clone.content.childNodes.length === 0 ||
143 clone.content.firstChild.content.childNodes.length === 0
144 );
145 })();
146
147 // NOTE: any browser that does not have template or ES6 features
148 // must load the full suite of polyfills.
149 if (
150 !window.Promise ||
151 !Array.from ||
152 !window.URL ||
153 !window.Symbol ||
154 needsTemplate
155 ) {
156 polyfills = ['sd-ce-pf'];
157 }
158
159 if (polyfills.length) {
160 // When the Trusted Types API is available, `policy` is a
161 // `TrustedTypePolicy` with functions for creating trusted HTML, scripts,
162 // and script URLs. This policy is used below to (a) approve the bundle URL
163 // string created by the loader that is assigned to a `<script>`'s `src`
164 // attribute, (b) approve a constant script string that is assigned to that
165 // `<script>'s `onload` attribute, and (c) approve the string of HTML that
166 // the loader reads from that `<script>`'s `outerHTML`.
167 //
168 // If the Trusted Types API is not available, the returned object exposes a
169 // similar interface to a `TrustedTypePolicy`, but all of its functions are
170 // the identity function.
171 var policy = (function () {
172 var identity = function (x) {
173 return x;
174 };
175 var policyOptions = {
176 createHTML: identity,
177 createScript: identity,
178 createScriptURL: identity,
179 };
180 var policy =
181 window.trustedTypes &&
182 window.trustedTypes.createPolicy('webcomponents-loader', policyOptions);
183 return policy || policyOptions;
184 })();
185
186 var url;
187 var polyfillFile = 'bundles/webcomponents-' + polyfills.join('-') + '.js';
188
189 // Load it from the right place.
190 if (window.WebComponents.root) {
191 url = window.WebComponents.root + polyfillFile;
192 if (
193 window.trustedTypes &&
194 window.trustedTypes.isScriptURL(window.WebComponents.root)
195 ) {
196 url = policy.createScriptURL(url);
197 }
198 } else {
199 var script = document.querySelector('script[src*="' + name + '"]');
200 // Load it from the right place.
201 url = policy.createScriptURL(script.src.replace(name, polyfillFile));
202 }
203
204 var newScript = document.createElement('script');
205 newScript.src = url;
206 // if readyState is 'loading', this script is synchronous
207 if (document.readyState === 'loading') {
208 // make sure custom elements are batched whenever parser gets to the injected script
209 newScript.setAttribute(
210 'onload',
211 policy.createScript('window.WebComponents._batchCustomElements()')
212 );
213 document.write(policy.createHTML(newScript.outerHTML));
214 document.addEventListener('DOMContentLoaded', ready);
215 } else {
216 newScript.addEventListener('load', function () {
217 asyncReady();
218 });
219 newScript.addEventListener('error', function () {
220 throw new Error('Could not load polyfill bundle' + url);
221 });
222 document.head.appendChild(newScript);
223 }
224 } else {
225 // if readyState is 'complete', script is loaded imperatively on a spec-compliant browser, so just fire WCR
226 if (document.readyState === 'complete') {
227 polyfillsLoaded = true;
228 fireEvent();
229 } else {
230 // this script may come between DCL and load, so listen for both, and cancel load listener if DCL fires
231 window.addEventListener('load', ready);
232 window.addEventListener('DOMContentLoaded', function () {
233 window.removeEventListener('load', ready);
234 ready();
235 });
236 }
237 }
238})();