webcomponents-loader.js 6.0 KB

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