1// Copyright 2014 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5if (!_flutter) { 6 var _flutter = {}; 7} 8_flutter.loader = null; 9 10(function () { 11 "use strict"; 12 13 const baseUri = ensureTrailingSlash(getBaseURI()); 14 15 function getBaseURI() { 16 const base = document.querySelector("base"); 17 return (base && base.getAttribute("href")) || ""; 18 } 19 20 function ensureTrailingSlash(uri) { 21 if (uri == "") { 22 return uri; 23 } 24 return uri.endsWith("/") ? uri : `${uri}/`; 25 } 26 27 /** 28 * Wraps `promise` in a timeout of the given `duration` in ms. 29 * 30 * Resolves/rejects with whatever the original `promises` does, or rejects 31 * if `promise` takes longer to complete than `duration`. In that case, 32 * `debugName` is used to compose a legible error message. 33 * 34 * If `duration` is < 0, the original `promise` is returned unchanged. 35 * @param {Promise} promise 36 * @param {number} duration 37 * @param {string} debugName 38 * @returns {Promise} a wrapped promise. 39 */ 40 async function timeout(promise, duration, debugName) { 41 if (duration < 0) { 42 return promise; 43 } 44 let timeoutId; 45 const _clock = new Promise((_, reject) => { 46 timeoutId = setTimeout(() => { 47 reject( 48 new Error( 49 `${debugName} took more than ${duration}ms to resolve. Moving on.`, 50 { 51 cause: timeout, 52 } 53 ) 54 ); 55 }, duration); 56 }); 57 58 return Promise.race([promise, _clock]).finally(() => { 59 clearTimeout(timeoutId); 60 }); 61 } 62 63 /** 64 * Handles the creation of a TrustedTypes `policy` that validates URLs based 65 * on an (optional) incoming array of RegExes. 66 */ 67 class FlutterTrustedTypesPolicy { 68 /** 69 * Constructs the policy. 70 * @param {[RegExp]} validPatterns the patterns to test URLs 71 * @param {String} policyName the policy name (optional) 72 */ 73 constructor(validPatterns, policyName = "flutter-js") { 74 const patterns = validPatterns || [ 75 /\.js$/, 76 ]; 77 if (window.trustedTypes) { 78 this.policy = trustedTypes.createPolicy(policyName, { 79 createScriptURL: function(url) { 80 const parsed = new URL(url, window.location); 81 const file = parsed.pathname.split("/").pop(); 82 const matches = patterns.some((pattern) => pattern.test(file)); 83 if (matches) { 84 return parsed.toString(); 85 } 86 console.error( 87 "URL rejected by TrustedTypes policy", 88 policyName, ":", url, "(download prevented)"); 89 } 90 }); 91 } 92 } 93 } 94 95 /** 96 * Handles loading/reloading Flutter's service worker, if configured. 97 * 98 * @see: https://developers.google.com/web/fundamentals/primers/service-workers 99 */ 100 class FlutterServiceWorkerLoader { 101 /** 102 * Injects a TrustedTypesPolicy (or undefined if the feature is not supported). 103 * @param {TrustedTypesPolicy | undefined} policy 104 */ 105 setTrustedTypesPolicy(policy) { 106 this._ttPolicy = policy; 107 } 108 109 /** 110 * Returns a Promise that resolves when the latest Flutter service worker, 111 * configured by `settings` has been loaded and activated. 112 * 113 * Otherwise, the promise is rejected with an error message. 114 * @param {*} settings Service worker settings 115 * @returns {Promise} that resolves when the latest serviceWorker is ready. 116 */ 117 loadServiceWorker(settings) { 118 if (settings == null) { 119 // In the future, settings = null -> uninstall service worker? 120 console.debug("Null serviceWorker configuration. Skipping."); 121 return Promise.resolve(); 122 } 123 if (!("serviceWorker" in navigator)) { 124 let errorMessage = "Service Worker API unavailable."; 125 if (!window.isSecureContext) { 126 errorMessage += "\nThe current context is NOT secure." 127 errorMessage += "\nRead more: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts"; 128 } 129 return Promise.reject( 130 new Error(errorMessage) 131 ); 132 } 133 const { 134 serviceWorkerVersion, 135 serviceWorkerUrl = `${baseUri}flutter_service_worker.js?v=${serviceWorkerVersion}`, 136 timeoutMillis = 4000, 137 } = settings; 138 139 // Apply the TrustedTypes policy, if present. 140 let url = serviceWorkerUrl; 141 if (this._ttPolicy != null) { 142 url = this._ttPolicy.createScriptURL(url); 143 } 144 145 const serviceWorkerActivation = navigator.serviceWorker 146 .register(url) 147 .then(this._getNewServiceWorker) 148 .then(this._waitForServiceWorkerActivation); 149 150 // Timeout race promise 151 return timeout( 152 serviceWorkerActivation, 153 timeoutMillis, 154 "prepareServiceWorker" 155 ); 156 } 157 158 /** 159 * Returns the latest service worker for the given `serviceWorkerRegistrationPromise`. 160 * 161 * This might return the current service worker, if there's no new service worker 162 * awaiting to be installed/updated. 163 * 164 * @param {Promise<ServiceWorkerRegistration>} serviceWorkerRegistrationPromise 165 * @returns {Promise<ServiceWorker>} 166 */ 167 async _getNewServiceWorker(serviceWorkerRegistrationPromise) { 168 const reg = await serviceWorkerRegistrationPromise; 169 170 if (!reg.active && (reg.installing || reg.waiting)) { 171 // No active web worker and we have installed or are installing 172 // one for the first time. Simply wait for it to activate. 173 console.debug("Installing/Activating first service worker."); 174 return reg.installing || reg.waiting; 175 } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) { 176 // When the app updates the serviceWorkerVersion changes, so we 177 // need to ask the service worker to update.
178 return reg.update().then((newReg) => { 179 console.debug("Updating service worker."); 180 return newReg.installing || newReg.waiting || newReg.active; 181 }); 182 } else { 183 console.debug("Loading from existing service worker."); 184 return reg.active; 185 } 186 } 187 188 /** 189 * Returns a Promise that resolves when the `latestServiceWorker` changes its 190 * state to "activated". 191 * 192 * @param {Promise<ServiceWorker>} latestServiceWorkerPromise 193 * @returns {Promise<void>} 194 */ 195 async _waitForServiceWorkerActivation(latestServiceWorkerPromise) { 196 const serviceWorker = await latestServiceWorkerPromise; 197 198 if (!serviceWorker || serviceWorker.state == "activated") { 199 if (!serviceWorker) { 200 return Promise.reject( 201 new Error("Cannot activate a null service worker!") 202 ); 203 } else { 204 console.debug("Service worker already active."); 205 return Promise.resolve(); 206 } 207 } 208 return new Promise((resolve, _) => { 209 serviceWorker.addEventListener("statechange", () => { 210 if (serviceWorker.state == "activated") { 211 console.debug("Activated new service worker."); 212 resolve(); 213 } 214 }); 215 }); 216 } 217 } 218 219 /** 220 * Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying 221 * the user when Flutter is ready, through `didCreateEngineInitializer`. 222 * 223 * @see https://docs.flutter.dev/development/platform-integration/web/initialization 224 */ 225 class FlutterEntrypointLoader { 226 /** 227 * Creates a FlutterEntrypointLoader. 228 */ 229 constructor() { 230 // Watchdog to prevent injecting the main entrypoint multiple times. 231 this._scriptLoaded = false; 232 } 233 234 /** 235 * Injects a TrustedTypesPolicy (or undefined if the feature is not supported). 236 * @param {TrustedTypesPolicy | undefined} policy 237 */ 238 setTrustedTypesPolicy(policy) { 239 this._ttPolicy = policy; 240 } 241 242 /** 243 * Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a 244 * user-specified `onEntrypointLoaded` callback with an EngineInitializer 245 * object when it's done. 246 * 247 * @param {*} options 248 * @returns {Promise | undefined} that will eventually resolve with an 249 * EngineInitializer, or will be rejected with the error caused by the loader. 250 * Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`. 251 */ 252 async loadEntrypoint(options) { 253 const { entrypointUrl = `${baseUri}main.dart.js`, onEntrypointLoaded } = 254 options || {}; 255 256 return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded); 257 } 258 259 /** 260 * Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded` 261 * function supplied by the user (if needed). 262 * 263 * Called by Flutter through `_flutter.loader.didCreateEngineInitializer` method, 264 * which is bound to the correct instance of the FlutterEntrypointLoader by 265 * the FlutterLoader object. 266 * 267 * @param {Function} engineInitializer @see https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/js_interop/js_loader.dart#L42 268 */ 269 didCreateEngineInitializer(engineInitializer) { 270 if (typeof this._didCreateEngineInitializerResolve === "function") { 271 this._didCreateEngineInitializerResolve(engineInitializer); 272 // Remove the resolver after the first time, so Flutter Web can hot restart. 273 this._didCreateEngineInitializerResolve = null; 274 // Make the engine revert to "auto" initialization on hot restart. 275 delete _flutter.loader.didCreateEngineInitializer; 276 } 277 if (typeof this._onEntrypointLoaded === "function") { 278 this._onEntrypointLoaded(engineInitializer); 279 } 280 } 281 282 /** 283 * Injects a script tag into the DOM, and configures this loader to be able to 284 * handle the "entrypoint loaded" notifications received from Flutter web. 285 * 286 * @param {string} entrypointUrl the URL of the script that will initialize 287 * Flutter. 288 * @param {Function} onEntrypointLoaded a callback that will be called when 289 * Flutter web notifies this object that the entrypoint is 290 * loaded. 291 * @returns {Promise | undefined} a Promise that resolves when the entrypoint 292 * is loaded, or undefined if `onEntrypointLoaded` 293 * is a function. 294 */ 295 _loadEntrypoint(entrypointUrl, onEntrypointLoaded) { 296 const useCallback = typeof onEntrypointLoaded === "function"; 297 298 if (!this._scriptLoaded) { 299 this._scriptLoaded = true; 300 const scriptTag = this._createScriptTag(entrypointUrl); 301 if (useCallback) { 302 // Just inject the script tag, and return nothing; Flutter will call 303 // `didCreateEngineInitializer` when it's done. 304 console.debug("Injecting <script> tag. Using callback."); 305 this._onEntrypointLoaded = onEntrypointLoaded; 306 document.body.append(scriptTag); 307 } else { 308 // Inject the script tag and return a promise that will get resolved 309 // with the EngineInitializer object from Flutter when it calls 310 // `didCreateEngineInitializer` later. 311 return new Promise((resolve, reject) => { 312 console.debug( 313 "Injecting <script> tag. Using Promises. Use the callback approach instead!" 314 ); 315 this._didCreateEngineInitializerResolve = resolve; 316 scriptTag.addEventListener("error", reject); 317 document.body.append(scriptTag); 318 }); 319 } 320 } 321 } 322 323 /** 324 * Creates a script tag for the given URL. 325 * @param {string} url 326 * @returns {HTMLScriptElement} 327 */ 328 _createScriptTag(url) { 329 const scriptTag = document.createElement("script"); 330 scriptTag.type = "application/javascript"; 331 // Apply TrustedTypes validation, if available. 332 let trustedUrl = url; 333 if (this._ttPolicy != null) { 334 trustedUrl = this._ttPolicy.createScriptURL(url); 335 } 336 scriptTag.src = trustedUrl; 337 return scriptTag; 338 } 339 } 340 341 /** 342 * The public interface of _flutter.loader. Exposes two methods: 343 * * loadEntrypoint (which coordinates the default Flutter web loading procedure) 344 * * didCreateEngineInitializer (which is called by Flutter to notify that its 345 * Engine is ready to be initialized) 346 */ 347 class FlutterLoader { 348 /** 349 * Initializes the Flutter web app. 350 * @param {*} options 351 * @returns {Promise?} a (Deprecated) Promise that will eventually resolve 352 * with an EngineInitializer, or will be rejected with 353 * any error caused by the loader. Or Null, if the user 354 * supplies an `onEntrypointLoaded` Function as an option. 355 */ 356 async loadEntrypoint(options) { 357 const { serviceWorker, ...entrypoint } = options || {}; 358 359 // A Trusted Types policy that is going to be used by the loader. 360 const flutterTT = new FlutterTrustedTypesPolicy(); 361 362 // The FlutterServiceWorkerLoader instance could be injected as a dependency 363 // (and dynamically imported from a module if not present). 364 const serviceWorkerLoader = new FlutterServiceWorkerLoader(); 365 serviceWorkerLoader.setTrustedTypesPolicy(flutterTT.policy); 366 await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => { 367 // Regardless of what happens with the injection of the SW, the show must go on 368 console.warn("Exception while loading service worker:", e); 369 }); 370 371 // The FlutterEntrypointLoader instance could be injected as a dependency 372 // (and dynamically imported from a module if not present). 373 const entrypointLoader = new FlutterEntrypointLoader(); 374 entrypointLoader.setTrustedTypesPolicy(flutterTT.policy); 375 // Install the `didCreateEngineInitializer` listener where Flutter web expects it to be. 376 this.didCreateEngineInitializer = 377 entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader); 378 return entrypointLoader.loadEntrypoint(entrypoint); 379 } 380 } 381 382 _flutter.loader = new FlutterLoader(); 383})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.