[web] Add onEntrypointLoaded to FlutterLoader. (#108776)
This commit is contained in:
parent
f7b0023439
commit
04f7ea8459
@ -29,6 +29,7 @@ enum ServiceWorkerTestType {
|
|||||||
withoutFlutterJs,
|
withoutFlutterJs,
|
||||||
withFlutterJs,
|
withFlutterJs,
|
||||||
withFlutterJsShort,
|
withFlutterJsShort,
|
||||||
|
withFlutterJsEntrypointLoadedEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run a web service worker test as a standalone Dart program.
|
// Run a web service worker test as a standalone Dart program.
|
||||||
@ -36,9 +37,11 @@ Future<void> main() async {
|
|||||||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
||||||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
||||||
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||||
|
await runWebServiceWorkerTest(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||||
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent);
|
||||||
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
|
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +70,9 @@ String _testTypeToIndexFile(ServiceWorkerTestType type) {
|
|||||||
case ServiceWorkerTestType.withFlutterJsShort:
|
case ServiceWorkerTestType.withFlutterJsShort:
|
||||||
indexFile = 'index_with_flutterjs_short.html';
|
indexFile = 'index_with_flutterjs_short.html';
|
||||||
break;
|
break;
|
||||||
|
case ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent:
|
||||||
|
indexFile = 'index_with_flutterjs_entrypoint_loaded.html';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return indexFile;
|
return indexFile;
|
||||||
}
|
}
|
||||||
|
@ -1092,9 +1092,11 @@ Future<void> _runWebLongRunningTests() async {
|
|||||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
||||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||||
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||||
|
() => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||||
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent),
|
||||||
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
|
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
|
||||||
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
|
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
|
||||||
() => _runWebStackTraceTest('release', 'lib/stack_trace.dart'),
|
() => _runWebStackTraceTest('release', 'lib/stack_trace.dart'),
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<!-- Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
Use of this source code is governed by a BSD-style license that can be
|
||||||
|
found in the LICENSE file. -->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
|
||||||
|
<title>Integration test. App load with flutter.js and onEntrypointLoaded API</title>
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Web Test">
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<script>
|
||||||
|
// The value below is injected by flutter build, do not touch.
|
||||||
|
var serviceWorkerVersion = null;
|
||||||
|
</script>
|
||||||
|
<!-- This script adds the flutter initialization JS code -->
|
||||||
|
<script src="flutter.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function(ev) {
|
||||||
|
// Download main.dart.js
|
||||||
|
_flutter.loader.loadEntrypoint({
|
||||||
|
onEntrypointLoaded: onEntrypointLoaded,
|
||||||
|
serviceWorker: {
|
||||||
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Once the entrypoint is ready, do things!
|
||||||
|
async function onEntrypointLoaded(engineInitializer) {
|
||||||
|
const appRunner = await engineInitializer.initializeEngine();
|
||||||
|
appRunner.runApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -8,169 +8,301 @@
|
|||||||
/// flutter.js should be completely static, so **do not use any parameter or
|
/// flutter.js should be completely static, so **do not use any parameter or
|
||||||
/// environment variable to generate this file**.
|
/// environment variable to generate this file**.
|
||||||
String generateFlutterJsFile() {
|
String generateFlutterJsFile() {
|
||||||
return '''
|
return r'''
|
||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
/**
|
|
||||||
* This script installs service_worker.js to provide PWA functionality to
|
|
||||||
* application. For more information, see:
|
|
||||||
* https://developers.google.com/web/fundamentals/primers/service-workers
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!_flutter) {
|
if (!_flutter) {
|
||||||
var _flutter = {};
|
var _flutter = {};
|
||||||
}
|
}
|
||||||
_flutter.loader = null;
|
_flutter.loader = null;
|
||||||
|
|
||||||
(function() {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
class FlutterLoader {
|
|
||||||
/**
|
/**
|
||||||
* Creates a FlutterLoader, and initializes its instance methods.
|
* Wraps `promise` in a timeout of the given `duration` in ms.
|
||||||
|
*
|
||||||
|
* Resolves/rejects with whatever the original `promises` does, or rejects
|
||||||
|
* if `promise` takes longer to complete than `duration`. In that case,
|
||||||
|
* `debugName` is used to compose a legible error message.
|
||||||
|
*
|
||||||
|
* If `duration` is < 0, the original `promise` is returned unchanged.
|
||||||
|
* @param {Promise} promise
|
||||||
|
* @param {number} duration
|
||||||
|
* @param {string} debugName
|
||||||
|
* @returns {Promise} a wrapped promise.
|
||||||
|
*/
|
||||||
|
async function timeout(promise, duration, debugName) {
|
||||||
|
if (duration < 0) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
let timeoutId;
|
||||||
|
const _clock = new Promise((_, reject) => {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`${debugName} took more than ${duration}ms to resolve. Moving on.`,
|
||||||
|
{
|
||||||
|
cause: timeout,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, duration);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.race([promise, _clock]).finally(() => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles loading/reloading Flutter's service worker, if configured.
|
||||||
|
*
|
||||||
|
* @see: https://developers.google.com/web/fundamentals/primers/service-workers
|
||||||
|
*/
|
||||||
|
class FlutterServiceWorkerLoader {
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves when the latest Flutter service worker,
|
||||||
|
* configured by `settings` has been loaded and activated.
|
||||||
|
*
|
||||||
|
* Otherwise, the promise is rejected with an error message.
|
||||||
|
* @param {*} settings Service worker settings
|
||||||
|
* @returns {Promise} that resolves when the latest serviceWorker is ready.
|
||||||
|
*/
|
||||||
|
loadServiceWorker(settings) {
|
||||||
|
if (!("serviceWorker" in navigator) || settings == null) {
|
||||||
|
// In the future, settings = null -> uninstall service worker?
|
||||||
|
return Promise.reject(
|
||||||
|
new Error("Service worker not supported (or configured).")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
serviceWorkerVersion,
|
||||||
|
serviceWorkerUrl = "flutter_service_worker.js?v=" +
|
||||||
|
serviceWorkerVersion,
|
||||||
|
timeoutMillis = 4000,
|
||||||
|
} = settings;
|
||||||
|
|
||||||
|
const serviceWorkerActivation = navigator.serviceWorker
|
||||||
|
.register(serviceWorkerUrl)
|
||||||
|
.then(this._getNewServiceWorker)
|
||||||
|
.then(this._waitForServiceWorkerActivation);
|
||||||
|
|
||||||
|
// Timeout race promise
|
||||||
|
return timeout(
|
||||||
|
serviceWorkerActivation,
|
||||||
|
timeoutMillis,
|
||||||
|
"prepareServiceWorker"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latest service worker for the given `serviceWorkerRegistrationPromise`.
|
||||||
|
*
|
||||||
|
* This might return the current service worker, if there's no new service worker
|
||||||
|
* awaiting to be installed/updated.
|
||||||
|
*
|
||||||
|
* @param {Promise<ServiceWorkerRegistration>} serviceWorkerRegistrationPromise
|
||||||
|
* @returns {Promise<ServiceWorker>}
|
||||||
|
*/
|
||||||
|
async _getNewServiceWorker(serviceWorkerRegistrationPromise) {
|
||||||
|
const reg = await serviceWorkerRegistrationPromise;
|
||||||
|
|
||||||
|
if (!reg.active && (reg.installing || reg.waiting)) {
|
||||||
|
// No active web worker and we have installed or are installing
|
||||||
|
// one for the first time. Simply wait for it to activate.
|
||||||
|
console.debug("Installing/Activating first service worker.");
|
||||||
|
return reg.installing || reg.waiting;
|
||||||
|
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
||||||
|
// When the app updates the serviceWorkerVersion changes, so we
|
||||||
|
// need to ask the service worker to update.
|
||||||
|
return reg.update().then((newReg) => {
|
||||||
|
console.debug("Updating service worker.");
|
||||||
|
return newReg.installing || newReg.waiting || newReg.active;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.debug("Loading from existing service worker.");
|
||||||
|
return reg.active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves when the `latestServiceWorker` changes its
|
||||||
|
* state to "activated".
|
||||||
|
*
|
||||||
|
* @param {Promise<ServiceWorker>} latestServiceWorkerPromise
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async _waitForServiceWorkerActivation(latestServiceWorkerPromise) {
|
||||||
|
const serviceWorker = await latestServiceWorkerPromise;
|
||||||
|
|
||||||
|
if (!serviceWorker || serviceWorker.state == "activated") {
|
||||||
|
if (!serviceWorker) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error("Cannot activate a null service worker!")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.debug("Service worker already active.");
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
serviceWorker.addEventListener("statechange", () => {
|
||||||
|
if (serviceWorker.state == "activated") {
|
||||||
|
console.debug("Activated new service worker.");
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying
|
||||||
|
* the user when Flutter is ready, through `didCreateEngineInitializer`.
|
||||||
|
*
|
||||||
|
* @see https://docs.flutter.dev/development/platform-integration/web/initialization
|
||||||
|
*/
|
||||||
|
class FlutterEntrypointLoader {
|
||||||
|
/**
|
||||||
|
* Creates a FlutterEntrypointLoader.
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
// TODO: Move the below methods to "#private" once supported by all the browsers
|
|
||||||
// we support. In the meantime, we use the "revealing module" pattern.
|
|
||||||
|
|
||||||
// Watchdog to prevent injecting the main entrypoint multiple times.
|
// Watchdog to prevent injecting the main entrypoint multiple times.
|
||||||
this._scriptLoaded = null;
|
this._scriptLoaded = false;
|
||||||
|
|
||||||
// Resolver for the pending promise returned by loadEntrypoint.
|
|
||||||
this._didCreateEngineInitializerResolve = null;
|
|
||||||
|
|
||||||
// Called by Flutter web.
|
|
||||||
// Bound to `this` now, so "this" is preserved across JS <-> Flutter jumps.
|
|
||||||
this.didCreateEngineInitializer = this._didCreateEngineInitializer.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the main.dart.js with/without serviceWorker.
|
* Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
|
||||||
|
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
|
||||||
|
* object when it's done.
|
||||||
|
*
|
||||||
* @param {*} options
|
* @param {*} options
|
||||||
* @returns a Promise that will eventually resolve with an EngineInitializer,
|
* @returns {Promise | undefined} that will eventually resolve with an
|
||||||
* or will be rejected with the error caused by the loader.
|
* EngineInitializer, or will be rejected with the error caused by the loader.
|
||||||
|
* Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`.
|
||||||
*/
|
*/
|
||||||
loadEntrypoint(options) {
|
async loadEntrypoint(options) {
|
||||||
const {
|
const { entrypointUrl = "main.dart.js", onEntrypointLoaded } =
|
||||||
entrypointUrl = "main.dart.js",
|
options || {};
|
||||||
serviceWorker,
|
|
||||||
} = (options || {});
|
return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded);
|
||||||
return this._loadWithServiceWorker(entrypointUrl, serviceWorker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the promise created by loadEntrypoint.
|
* Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded`
|
||||||
* Called by Flutter through the public `didCreateEngineInitializer` method,
|
* function supplied by the user (if needed).
|
||||||
* which is bound to the correct instance of the FlutterLoader on the page.
|
*
|
||||||
* @param {*} engineInitializer
|
* Called by Flutter through `_flutter.loader.didCreateEngineInitializer` method,
|
||||||
|
* which is bound to the correct instance of the FlutterEntrypointLoader by
|
||||||
|
* the FlutterLoader object.
|
||||||
|
*
|
||||||
|
* @param {Function} engineInitializer @see https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/js_interop/js_loader.dart#L42
|
||||||
*/
|
*/
|
||||||
_didCreateEngineInitializer(engineInitializer) {
|
didCreateEngineInitializer(engineInitializer) {
|
||||||
if (typeof this._didCreateEngineInitializerResolve != "function") {
|
if (typeof this._didCreateEngineInitializerResolve === "function") {
|
||||||
console.warn("Do not call didCreateEngineInitializer by hand. Start with loadEntrypoint instead.");
|
|
||||||
}
|
|
||||||
this._didCreateEngineInitializerResolve(engineInitializer);
|
this._didCreateEngineInitializerResolve(engineInitializer);
|
||||||
// Remove the public method after it's done, so Flutter Web can hot restart.
|
// Remove the resolver after the first time, so Flutter Web can hot restart.
|
||||||
delete this.didCreateEngineInitializer;
|
this._didCreateEngineInitializerResolve = null;
|
||||||
|
}
|
||||||
|
if (typeof this._onEntrypointLoaded === "function") {
|
||||||
|
this._onEntrypointLoaded(engineInitializer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadEntrypoint(entrypointUrl) {
|
/**
|
||||||
|
* Injects a script tag into the DOM, and configures this loader to be able to
|
||||||
|
* handle the "entrypoint loaded" notifications received from Flutter web.
|
||||||
|
*
|
||||||
|
* @param {string} entrypointUrl the URL of the script that will initialize
|
||||||
|
* Flutter.
|
||||||
|
* @param {Function} onEntrypointLoaded a callback that will be called when
|
||||||
|
* Flutter web notifies this object that the entrypoint is
|
||||||
|
* loaded.
|
||||||
|
* @returns {Promise | undefined} a Promise that resolves when the entrypoint
|
||||||
|
* is loaded, or undefined if `onEntrypointLoaded`
|
||||||
|
* is a function.
|
||||||
|
*/
|
||||||
|
_loadEntrypoint(entrypointUrl, onEntrypointLoaded) {
|
||||||
|
const useCallback = typeof onEntrypointLoaded === "function";
|
||||||
|
|
||||||
if (!this._scriptLoaded) {
|
if (!this._scriptLoaded) {
|
||||||
console.debug("Injecting <script> tag.");
|
this._scriptLoaded = true;
|
||||||
this._scriptLoaded = new Promise((resolve, reject) => {
|
const scriptTag = this._createScriptTag(entrypointUrl);
|
||||||
let scriptTag = document.createElement("script");
|
if (useCallback) {
|
||||||
scriptTag.src = entrypointUrl;
|
// Just inject the script tag, and return nothing; Flutter will call
|
||||||
scriptTag.type = "application/javascript";
|
// `didCreateEngineInitializer` when it's done.
|
||||||
// Cache the resolve, so it can be called from Flutter.
|
console.debug("Injecting <script> tag. Using callback.");
|
||||||
// Note: Flutter hot restart doesn't re-create this promise, so this
|
this._onEntrypointLoaded = onEntrypointLoaded;
|
||||||
// can only be called once. Instead, we need to model this as a stream
|
document.body.append(scriptTag);
|
||||||
// of `engineCreated` events coming from Flutter that are handled by JS.
|
} else {
|
||||||
|
// Inject the script tag and return a promise that will get resolved
|
||||||
|
// with the EngineInitializer object from Flutter when it calls
|
||||||
|
// `didCreateEngineInitializer` later.
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.debug(
|
||||||
|
"Injecting <script> tag. Using Promises. Use the callback approach instead!"
|
||||||
|
);
|
||||||
this._didCreateEngineInitializerResolve = resolve;
|
this._didCreateEngineInitializerResolve = resolve;
|
||||||
scriptTag.addEventListener("error", reject);
|
scriptTag.addEventListener("error", reject);
|
||||||
document.body.append(scriptTag);
|
document.body.append(scriptTag);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return this._scriptLoaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_waitForServiceWorkerActivation(serviceWorker, entrypointUrl) {
|
/**
|
||||||
if (!serviceWorker || serviceWorker.state == "activated") {
|
* Creates a script tag for the given URL.
|
||||||
if (!serviceWorker) {
|
* @param {string} url
|
||||||
console.warn("Cannot activate a null service worker.");
|
* @returns {HTMLScriptElement}
|
||||||
} else {
|
*/
|
||||||
console.debug("Service worker already active.");
|
_createScriptTag(url) {
|
||||||
|
const scriptTag = document.createElement("script");
|
||||||
|
scriptTag.type = "application/javascript";
|
||||||
|
scriptTag.src = url;
|
||||||
|
return scriptTag;
|
||||||
}
|
}
|
||||||
return this._loadEntrypoint(entrypointUrl);
|
|
||||||
}
|
|
||||||
return new Promise((resolve, _) => {
|
|
||||||
serviceWorker.addEventListener("statechange", () => {
|
|
||||||
if (serviceWorker.state == "activated") {
|
|
||||||
console.debug("Installed new service worker.");
|
|
||||||
resolve(this._loadEntrypoint(entrypointUrl));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadWithServiceWorker(entrypointUrl, serviceWorkerOptions) {
|
/**
|
||||||
if (!("serviceWorker" in navigator) || serviceWorkerOptions == null) {
|
* The public interface of _flutter.loader. Exposes two methods:
|
||||||
console.warn("Service worker not supported (or configured).", serviceWorkerOptions);
|
* * loadEntrypoint (which coordinates the default Flutter web loading procedure)
|
||||||
return this._loadEntrypoint(entrypointUrl);
|
* * didCreateEngineInitializer (which is called by Flutter to notify that its
|
||||||
}
|
* Engine is ready to be initialized)
|
||||||
|
*/
|
||||||
|
class FlutterLoader {
|
||||||
|
/**
|
||||||
|
* Initializes the Flutter web app.
|
||||||
|
* @param {*} options
|
||||||
|
* @returns {Promise?} a (Deprecated) Promise that will eventually resolve
|
||||||
|
* with an EngineInitializer, or will be rejected with
|
||||||
|
* any error caused by the loader. Or Null, if the user
|
||||||
|
* supplies an `onEntrypointLoaded` Function as an option.
|
||||||
|
*/
|
||||||
|
async loadEntrypoint(options) {
|
||||||
|
const { serviceWorker, ...entrypoint } = options || {};
|
||||||
|
|
||||||
const {
|
// The FlutterServiceWorkerLoader instance could be injected as a dependency
|
||||||
serviceWorkerVersion,
|
// (and dynamically imported from a module if not present).
|
||||||
timeoutMillis = 4000,
|
const serviceWorkerLoader = new FlutterServiceWorkerLoader();
|
||||||
} = serviceWorkerOptions;
|
await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => {
|
||||||
|
// Regardless of what happens with the injection of the SW, the show must go on
|
||||||
let serviceWorkerUrl = "flutter_service_worker.js?v=" + serviceWorkerVersion;
|
console.warn("Exception while loading service worker:", e);
|
||||||
let loader = navigator.serviceWorker.register(serviceWorkerUrl)
|
|
||||||
.then((reg) => {
|
|
||||||
if (!reg.active && (reg.installing || reg.waiting)) {
|
|
||||||
// No active web worker and we have installed or are installing
|
|
||||||
// one for the first time. Simply wait for it to activate.
|
|
||||||
let sw = reg.installing || reg.waiting;
|
|
||||||
return this._waitForServiceWorkerActivation(sw, entrypointUrl);
|
|
||||||
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
|
||||||
// When the app updates the serviceWorkerVersion changes, so we
|
|
||||||
// need to ask the service worker to update.
|
|
||||||
console.debug("New service worker available.");
|
|
||||||
return reg.update().then((reg) => {
|
|
||||||
console.debug("Service worker updated.");
|
|
||||||
let sw = reg.installing || reg.waiting || reg.active;
|
|
||||||
return this._waitForServiceWorkerActivation(sw, entrypointUrl);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Existing service worker is still good.
|
|
||||||
console.debug("Loading app from service worker.");
|
|
||||||
return this._loadEntrypoint(entrypointUrl);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
// Some exception happened while registering/activating the service worker.
|
|
||||||
console.warn("Failed to register or activate service worker:", error);
|
|
||||||
return this._loadEntrypoint(entrypointUrl);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Timeout race promise
|
// The FlutterEntrypointLoader instance could be injected as a dependency
|
||||||
let timeout;
|
// (and dynamically imported from a module if not present).
|
||||||
if (timeoutMillis > 0) {
|
const entrypointLoader = new FlutterEntrypointLoader();
|
||||||
timeout = new Promise((resolve, _) => {
|
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
|
||||||
setTimeout(() => {
|
this.didCreateEngineInitializer =
|
||||||
if (!this._scriptLoaded) {
|
entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader);
|
||||||
console.warn("Loading from service worker timed out after", timeoutMillis, "milliseconds.");
|
return entrypointLoader.loadEntrypoint(entrypoint);
|
||||||
resolve(this._loadEntrypoint(entrypointUrl));
|
|
||||||
}
|
|
||||||
}, timeoutMillis);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.race([loader, timeout]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_flutter.loader = new FlutterLoader();
|
_flutter.loader = new FlutterLoader();
|
||||||
}());
|
})();
|
||||||
|
|
||||||
''';
|
''';
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user