Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
d5dbcb70
Unverified
Commit
d5dbcb70
authored
Feb 10, 2023
by
Mouad Debbar
Committed by
GitHub
Feb 10, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Revert "[web] Move JS content to its own `.js` files (#117691)" (#120275)" (#120363)
This reverts commit
0fb4406c
.
parent
65fd924d
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
667 additions
and
568 deletions
+667
-568
artifacts.dart
packages/flutter_tools/lib/src/artifacts.dart
+39
-0
web.dart
packages/flutter_tools/lib/src/build_system/targets/web.dart
+7
-1
devfs_web.dart
packages/flutter_tools/lib/src/isolated/devfs_web.dart
+4
-1
flutter_js.dart
...flutter_tools/lib/src/web/file_generators/flutter_js.dart
+8
-379
flutter_service_worker_js.dart
...ib/src/web/file_generators/flutter_service_worker_js.dart
+19
-179
flutter.js
...s/flutter_tools/lib/src/web/file_generators/js/flutter.js
+375
-0
flutter_service_worker.js
.../lib/src/web/file_generators/js/flutter_service_worker.js
+172
-0
web_test.dart
...ols/test/general.shard/build_system/targets/web_test.dart
+37
-7
project.dart
...utter_tools/test/integration.shard/test_data/project.dart
+6
-1
No files found.
packages/flutter_tools/lib/src/artifacts.dart
View file @
d5dbcb70
...
@@ -9,6 +9,7 @@ import 'base/common.dart';
...
@@ -9,6 +9,7 @@ import 'base/common.dart';
import
'base/file_system.dart'
;
import
'base/file_system.dart'
;
import
'base/os.dart'
;
import
'base/os.dart'
;
import
'base/platform.dart'
;
import
'base/platform.dart'
;
import
'base/user_messages.dart'
;
import
'base/utils.dart'
;
import
'base/utils.dart'
;
import
'build_info.dart'
;
import
'build_info.dart'
;
import
'cache.dart'
;
import
'cache.dart'
;
...
@@ -62,6 +63,9 @@ enum Artifact {
...
@@ -62,6 +63,9 @@ enum Artifact {
/// Tools related to subsetting or icon font files.
/// Tools related to subsetting or icon font files.
fontSubset
,
fontSubset
,
constFinder
,
constFinder
,
/// The location of file generators.
flutterToolsFileGenerators
,
}
}
/// A subset of [Artifact]s that are platform and build mode independent
/// A subset of [Artifact]s that are platform and build mode independent
...
@@ -202,6 +206,8 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod
...
@@ -202,6 +206,8 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod
return
'font-subset
$exe
'
;
return
'font-subset
$exe
'
;
case
Artifact
.
constFinder
:
case
Artifact
.
constFinder
:
return
'const_finder.dart.snapshot'
;
return
'const_finder.dart.snapshot'
;
case
Artifact
.
flutterToolsFileGenerators
:
return
''
;
}
}
}
}
...
@@ -525,6 +531,8 @@ class CachedArtifacts implements Artifacts {
...
@@ -525,6 +531,8 @@ class CachedArtifacts implements Artifacts {
case
Artifact
.
windowsCppClientWrapper
:
case
Artifact
.
windowsCppClientWrapper
:
case
Artifact
.
windowsDesktopPath
:
case
Artifact
.
windowsDesktopPath
:
return
_getHostArtifactPath
(
artifact
,
platform
,
mode
);
return
_getHostArtifactPath
(
artifact
,
platform
,
mode
);
case
Artifact
.
flutterToolsFileGenerators
:
return
_getFileGeneratorsPath
();
}
}
}
}
...
@@ -562,6 +570,8 @@ class CachedArtifacts implements Artifacts {
...
@@ -562,6 +570,8 @@ class CachedArtifacts implements Artifacts {
case
Artifact
.
windowsCppClientWrapper
:
case
Artifact
.
windowsCppClientWrapper
:
case
Artifact
.
windowsDesktopPath
:
case
Artifact
.
windowsDesktopPath
:
return
_getHostArtifactPath
(
artifact
,
platform
,
mode
);
return
_getHostArtifactPath
(
artifact
,
platform
,
mode
);
case
Artifact
.
flutterToolsFileGenerators
:
return
_getFileGeneratorsPath
();
}
}
}
}
...
@@ -611,6 +621,8 @@ class CachedArtifacts implements Artifacts {
...
@@ -611,6 +621,8 @@ class CachedArtifacts implements Artifacts {
case
Artifact
.
windowsCppClientWrapper
:
case
Artifact
.
windowsCppClientWrapper
:
case
Artifact
.
windowsDesktopPath
:
case
Artifact
.
windowsDesktopPath
:
return
_getHostArtifactPath
(
artifact
,
platform
,
mode
);
return
_getHostArtifactPath
(
artifact
,
platform
,
mode
);
case
Artifact
.
flutterToolsFileGenerators
:
return
_getFileGeneratorsPath
();
}
}
}
}
...
@@ -685,6 +697,8 @@ class CachedArtifacts implements Artifacts {
...
@@ -685,6 +697,8 @@ class CachedArtifacts implements Artifacts {
case
Artifact
.
fuchsiaFlutterRunner
:
case
Artifact
.
fuchsiaFlutterRunner
:
case
Artifact
.
fuchsiaKernelCompiler
:
case
Artifact
.
fuchsiaKernelCompiler
:
throw
StateError
(
'Artifact
$artifact
not available for platform
$platform
.'
);
throw
StateError
(
'Artifact
$artifact
not available for platform
$platform
.'
);
case
Artifact
.
flutterToolsFileGenerators
:
return
_getFileGeneratorsPath
();
}
}
}
}
...
@@ -952,6 +966,8 @@ class CachedLocalEngineArtifacts implements Artifacts {
...
@@ -952,6 +966,8 @@ class CachedLocalEngineArtifacts implements Artifacts {
case
Artifact
.
dart2wasmSnapshot
:
case
Artifact
.
dart2wasmSnapshot
:
case
Artifact
.
frontendServerSnapshotForEngineDartSdk
:
case
Artifact
.
frontendServerSnapshotForEngineDartSdk
:
return
_fileSystem
.
path
.
join
(
_getDartSdkPath
(),
'bin'
,
'snapshots'
,
artifactFileName
);
return
_fileSystem
.
path
.
join
(
_getDartSdkPath
(),
'bin'
,
'snapshots'
,
artifactFileName
);
case
Artifact
.
flutterToolsFileGenerators
:
return
_getFileGeneratorsPath
();
}
}
}
}
...
@@ -1099,6 +1115,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
...
@@ -1099,6 +1115,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
case
Artifact
.
fuchsiaFlutterRunner
:
case
Artifact
.
fuchsiaFlutterRunner
:
case
Artifact
.
fontSubset
:
case
Artifact
.
fontSubset
:
case
Artifact
.
constFinder
:
case
Artifact
.
constFinder
:
case
Artifact
.
flutterToolsFileGenerators
:
break
;
break
;
}
}
}
}
...
@@ -1298,6 +1315,11 @@ class _TestArtifacts implements Artifacts {
...
@@ -1298,6 +1315,11 @@ class _TestArtifacts implements Artifacts {
BuildMode
?
mode
,
BuildMode
?
mode
,
EnvironmentType
?
environmentType
,
EnvironmentType
?
environmentType
,
})
{
})
{
// The path to file generators is the same even in the test environment.
if
(
artifact
==
Artifact
.
flutterToolsFileGenerators
)
{
return
_getFileGeneratorsPath
();
}
final
StringBuffer
buffer
=
StringBuffer
();
final
StringBuffer
buffer
=
StringBuffer
();
buffer
.
write
(
artifact
);
buffer
.
write
(
artifact
);
if
(
platform
!=
null
)
{
if
(
platform
!=
null
)
{
...
@@ -1340,3 +1362,20 @@ class _TestLocalEngine extends _TestArtifacts {
...
@@ -1340,3 +1362,20 @@ class _TestLocalEngine extends _TestArtifacts {
@override
@override
final
LocalEngineInfo
localEngineInfo
;
final
LocalEngineInfo
localEngineInfo
;
}
}
String
_getFileGeneratorsPath
(
)
{
final
String
flutterRoot
=
Cache
.
defaultFlutterRoot
(
fileSystem:
globals
.
localFileSystem
,
platform:
const
LocalPlatform
(),
userMessages:
UserMessages
(),
);
return
globals
.
localFileSystem
.
path
.
join
(
flutterRoot
,
'packages'
,
'flutter_tools'
,
'lib'
,
'src'
,
'web'
,
'file_generators'
,
);
}
packages/flutter_tools/lib/src/build_system/targets/web.dart
View file @
d5dbcb70
...
@@ -529,7 +529,10 @@ class WebBuiltInAssets extends Target {
...
@@ -529,7 +529,10 @@ class WebBuiltInAssets extends Target {
// Write the flutter.js file
// Write the flutter.js file
final
File
flutterJsFile
=
environment
.
outputDir
.
childFile
(
'flutter.js'
);
final
File
flutterJsFile
=
environment
.
outputDir
.
childFile
(
'flutter.js'
);
flutterJsFile
.
writeAsStringSync
(
flutter_js
.
generateFlutterJsFile
());
final
String
fileGeneratorsPath
=
globals
.
artifacts
!.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
flutterJsFile
.
writeAsStringSync
(
flutter_js
.
generateFlutterJsFile
(
fileGeneratorsPath
));
}
}
}
}
...
@@ -598,7 +601,10 @@ class WebServiceWorker extends Target {
...
@@ -598,7 +601,10 @@ class WebServiceWorker extends Target {
final
ServiceWorkerStrategy
serviceWorkerStrategy
=
_serviceWorkerStrategyFromString
(
final
ServiceWorkerStrategy
serviceWorkerStrategy
=
_serviceWorkerStrategyFromString
(
environment
.
defines
[
kServiceWorkerStrategy
],
environment
.
defines
[
kServiceWorkerStrategy
],
);
);
final
String
fileGeneratorsPath
=
globals
.
artifacts
!.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
final
String
serviceWorker
=
generateServiceWorker
(
final
String
serviceWorker
=
generateServiceWorker
(
fileGeneratorsPath
,
urlToHash
,
urlToHash
,
<
String
>[
<
String
>[
'main.dart.js'
,
'main.dart.js'
,
...
...
packages/flutter_tools/lib/src/isolated/devfs_web.dart
View file @
d5dbcb70
...
@@ -828,7 +828,10 @@ class WebDevFS implements DevFS {
...
@@ -828,7 +828,10 @@ class WebDevFS implements DevFS {
'stack_trace_mapper.js'
,
stackTraceMapper
.
readAsBytesSync
());
'stack_trace_mapper.js'
,
stackTraceMapper
.
readAsBytesSync
());
webAssetServer
.
writeFile
(
webAssetServer
.
writeFile
(
'manifest.json'
,
'{"info":"manifest not generated in run mode."}'
);
'manifest.json'
,
'{"info":"manifest not generated in run mode."}'
);
webAssetServer
.
writeFile
(
'flutter.js'
,
flutter_js
.
generateFlutterJsFile
());
final
String
fileGeneratorsPath
=
globals
.
artifacts
!
.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
webAssetServer
.
writeFile
(
'flutter.js'
,
flutter_js
.
generateFlutterJsFile
(
fileGeneratorsPath
));
webAssetServer
.
writeFile
(
'flutter_service_worker.js'
,
webAssetServer
.
writeFile
(
'flutter_service_worker.js'
,
'// Service worker not loaded in run mode.'
);
'// Service worker not loaded in run mode.'
);
webAssetServer
.
writeFile
(
webAssetServer
.
writeFile
(
...
...
packages/flutter_tools/lib/src/web/file_generators/flutter_js.dart
View file @
d5dbcb70
...
@@ -2,388 +2,17 @@
...
@@ -2,388 +2,17 @@
// 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.
import
'../../globals.dart'
as
globals
;
/// Generates the flutter.js file.
/// Generates the flutter.js file.
///
///
/// 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
(
String
fileGeneratorsPath
)
{
return
r''
'
final
String
flutterJsPath
=
globals
.
localFileSystem
.
path
.
join
(
// Copyright 2014 The Flutter Authors. All rights reserved.
fileGeneratorsPath
,
// Use of this source code is governed by a BSD-style license that can be
'js'
,
// found in the LICENSE file.
'flutter.js'
,
);
if (!_flutter) {
return
globals
.
localFileSystem
.
file
(
flutterJsPath
).
readAsStringSync
();
var _flutter = {};
}
_flutter.loader = null;
(function () {
"use strict";
const baseUri = ensureTrailingSlash(getBaseURI());
function getBaseURI() {
const base = document.querySelector("base");
return (base && base.getAttribute("href")) || "";
}
function ensureTrailingSlash(uri) {
if (uri == "") {
return uri;
}
return uri.endsWith("/") ? uri : `
${uri}
/`;
}
/**
* 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 the creation of a TrustedTypes `policy` that validates URLs based
* on an (optional) incoming array of RegExes.
*/
class FlutterTrustedTypesPolicy {
/**
* Constructs the policy.
* @param {[RegExp]} validPatterns the patterns to test URLs
* @param {String} policyName the policy name (optional)
*/
constructor(validPatterns, policyName = "flutter-js") {
const patterns = validPatterns || [
/
\
.dart
\
.js
$
/,
/^flutter_service_worker.js
$
/
];
if (window.trustedTypes) {
this.policy = trustedTypes.createPolicy(policyName, {
createScriptURL: function(url) {
const parsed = new URL(url, window.location);
const file = parsed.pathname.split("/").pop();
const matches = patterns.some((pattern) => pattern.test(file));
if (matches) {
return parsed.toString();
}
console.error(
"URL rejected by TrustedTypes policy",
policyName, ":", url, "(download prevented)");
}
});
}
}
}
/**
* Handles loading/reloading Flutter'
s
service
worker
,
if
configured
.
*
*
@see
:
https:
//developers.google.com/web/fundamentals/primers/service-workers
*/
class
FlutterServiceWorkerLoader
{
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy
(
policy
)
{
this
.
_ttPolicy
=
policy
;
}
/**
* 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
=
`$
{
baseUri
}
flutter_service_worker
.
js
?
v
=
$
{
serviceWorkerVersion
}
`
,
timeoutMillis
=
4000
,
}
=
settings
;
// Apply the TrustedTypes policy, if present.
let
url
=
serviceWorkerUrl
;
if
(
this
.
_ttPolicy
!=
null
)
{
url
=
this
.
_ttPolicy
.
createScriptURL
(
url
);
}
const
serviceWorkerActivation
=
navigator
.
serviceWorker
.
register
(
url
)
.
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
()
{
// Watchdog to prevent injecting the main entrypoint multiple times.
this
.
_scriptLoaded
=
false
;
}
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy
(
policy
)
{
this
.
_ttPolicy
=
policy
;
}
/**
* Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
* object when it's done.
*
* @param {*} options
* @returns {Promise | undefined} that will eventually resolve with an
* EngineInitializer, or will be rejected with the error caused by the loader.
* Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`.
*/
async
loadEntrypoint
(
options
)
{
const
{
entrypointUrl
=
`$
{
baseUri
}
main
.
dart
.
js
`
,
onEntrypointLoaded
}
=
options
||
{};
return
this
.
_loadEntrypoint
(
entrypointUrl
,
onEntrypointLoaded
);
}
/**
* Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded`
* function supplied by the user (if needed).
*
* 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
)
{
if
(
typeof
this
.
_didCreateEngineInitializerResolve
===
"function"
)
{
this
.
_didCreateEngineInitializerResolve
(
engineInitializer
);
// Remove the resolver after the first time, so Flutter Web can hot restart.
this
.
_didCreateEngineInitializerResolve
=
null
;
// Make the engine revert to "auto" initialization on hot restart.
delete
_flutter
.
loader
.
didCreateEngineInitializer
;
}
if
(
typeof
this
.
_onEntrypointLoaded
===
"function"
)
{
this
.
_onEntrypointLoaded
(
engineInitializer
);
}
}
/**
* 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
)
{
this
.
_scriptLoaded
=
true
;
const
scriptTag
=
this
.
_createScriptTag
(
entrypointUrl
);
if
(
useCallback
)
{
// Just inject the script tag, and return nothing; Flutter will call
// `didCreateEngineInitializer` when it's done.
console
.
debug
(
"Injecting <script> tag. Using callback."
);
this
.
_onEntrypointLoaded
=
onEntrypointLoaded
;
document
.
body
.
append
(
scriptTag
);
}
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
;
scriptTag
.
addEventListener
(
"error"
,
reject
);
document
.
body
.
append
(
scriptTag
);
});
}
}
}
/**
* Creates a script tag for the given URL.
* @param {string} url
* @returns {HTMLScriptElement}
*/
_createScriptTag
(
url
)
{
const
scriptTag
=
document
.
createElement
(
"script"
);
scriptTag
.
type
=
"application/javascript"
;
// Apply TrustedTypes validation, if available.
let
trustedUrl
=
url
;
if
(
this
.
_ttPolicy
!=
null
)
{
trustedUrl
=
this
.
_ttPolicy
.
createScriptURL
(
url
);
}
scriptTag
.
src
=
trustedUrl
;
return
scriptTag
;
}
}
/**
* The public interface of _flutter.loader. Exposes two methods:
* * loadEntrypoint (which coordinates the default Flutter web loading procedure)
* * 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
||
{};
// A Trusted Types policy that is going to be used by the loader.
const
flutterTT
=
new
FlutterTrustedTypesPolicy
();
// The FlutterServiceWorkerLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const
serviceWorkerLoader
=
new
FlutterServiceWorkerLoader
();
serviceWorkerLoader
.
setTrustedTypesPolicy
(
flutterTT
.
policy
);
await
serviceWorkerLoader
.
loadServiceWorker
(
serviceWorker
).
catch
(
e
=>
{
// Regardless of what happens with the injection of the SW, the show must go on
console
.
warn
(
"Exception while loading service worker:"
,
e
);
});
// The FlutterEntrypointLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const
entrypointLoader
=
new
FlutterEntrypointLoader
();
entrypointLoader
.
setTrustedTypesPolicy
(
flutterTT
.
policy
);
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
this
.
didCreateEngineInitializer
=
entrypointLoader
.
didCreateEngineInitializer
.
bind
(
entrypointLoader
);
return
entrypointLoader
.
loadEntrypoint
(
entrypoint
);
}
}
_flutter
.
loader
=
new
FlutterLoader
();
})();
''';
}
}
packages/flutter_tools/lib/src/web/file_generators/flutter_service_worker_js.dart
View file @
d5dbcb70
...
@@ -2,11 +2,14 @@
...
@@ -2,11 +2,14 @@
// 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.
import
'../../globals.dart'
as
globals
;
/// The caching strategy for the generated service worker.
/// The caching strategy for the generated service worker.
enum
ServiceWorkerStrategy
{
enum
ServiceWorkerStrategy
{
/// Download the app shell eagerly and all other assets lazily.
/// Download the app shell eagerly and all other assets lazily.
/// Prefer the offline cached version.
/// Prefer the offline cached version.
offlineFirst
,
offlineFirst
,
/// Do not generate a service worker,
/// Do not generate a service worker,
none
,
none
,
}
}
...
@@ -18,6 +21,7 @@ enum ServiceWorkerStrategy {
...
@@ -18,6 +21,7 @@ enum ServiceWorkerStrategy {
/// invalidation will automatically reactivate workers whenever a new
/// invalidation will automatically reactivate workers whenever a new
/// version is deployed.
/// version is deployed.
String
generateServiceWorker
(
String
generateServiceWorker
(
String
fileGeneratorsPath
,
Map
<
String
,
String
>
resources
,
Map
<
String
,
String
>
resources
,
List
<
String
>
coreBundle
,
{
List
<
String
>
coreBundle
,
{
required
ServiceWorkerStrategy
serviceWorkerStrategy
,
required
ServiceWorkerStrategy
serviceWorkerStrategy
,
...
@@ -25,185 +29,21 @@ String generateServiceWorker(
...
@@ -25,185 +29,21 @@ String generateServiceWorker(
if
(
serviceWorkerStrategy
==
ServiceWorkerStrategy
.
none
)
{
if
(
serviceWorkerStrategy
==
ServiceWorkerStrategy
.
none
)
{
return
''
;
return
''
;
}
}
return
'''
'
use
strict
';
const MANIFEST = '
flutter
-
app
-
manifest
';
const TEMP = '
flutter
-
temp
-
cache
';
const CACHE_NAME = '
flutter
-
app
-
cache
';
const RESOURCES = {
${resources.entries.map((MapEntry<String, String> entry) => '"${entry.key}
": "
${entry.value}
"'
).
join
(
",
\n
"
)}
};
// The application shell files that are downloaded before a service worker can
// start.
const
CORE
=
[
$
{
coreBundle
.
map
((
String
file
)
=>
'"
$file
"'
).
join
(
',
\n
'
)}];
// During install, the TEMP cache is populated with the application shell files.
self
.
addEventListener
(
"install"
,
(
event
)
=>
{
self
.
skipWaiting
();
return
event
.
waitUntil
(
caches
.
open
(
TEMP
).
then
((
cache
)
=>
{
return
cache
.
addAll
(
CORE
.
map
((
value
)
=>
new
Request
(
value
,
{
'cache'
:
'reload'
})));
})
);
});
// During activate, the cache is populated with the temp files downloaded in
// install. If this service worker is upgrading from one with a saved
// MANIFEST, then use this to retain unchanged resource files.
self
.
addEventListener
(
"activate"
,
function
(
event
)
{
return
event
.
waitUntil
(
async
function
()
{
try
{
var
contentCache
=
await
caches
.
open
(
CACHE_NAME
);
var
tempCache
=
await
caches
.
open
(
TEMP
);
var
manifestCache
=
await
caches
.
open
(
MANIFEST
);
var
manifest
=
await
manifestCache
.
match
(
'manifest'
);
// When there is no prior manifest, clear the entire cache.
if
(!
manifest
)
{
await
caches
.
delete
(
CACHE_NAME
);
contentCache
=
await
caches
.
open
(
CACHE_NAME
);
for
(
var
request
of
await
tempCache
.
keys
())
{
var
response
=
await
tempCache
.
match
(
request
);
await
contentCache
.
put
(
request
,
response
);
}
await
caches
.
delete
(
TEMP
);
// Save the manifest to make future upgrades efficient.
await
manifestCache
.
put
(
'manifest'
,
new
Response
(
JSON
.
stringify
(
RESOURCES
)));
// Claim client to enable caching on first launch
self
.
clients
.
claim
();
return
;
}
var
oldManifest
=
await
manifest
.
json
();
var
origin
=
self
.
location
.
origin
;
for
(
var
request
of
await
contentCache
.
keys
())
{
var
key
=
request
.
url
.
substring
(
origin
.
length
+
1
);
if
(
key
==
""
)
{
key
=
"/"
;
}
// If a resource from the old manifest is not in the new cache, or if
// the MD5 sum has changed, delete it. Otherwise the resource is left
// in the cache and can be reused by the new service worker.
if
(!
RESOURCES
[
key
]
||
RESOURCES
[
key
]
!=
oldManifest
[
key
])
{
await
contentCache
.
delete
(
request
);
}
}
// Populate the cache with the app shell TEMP files, potentially overwriting
// cache files preserved above.
for
(
var
request
of
await
tempCache
.
keys
())
{
var
response
=
await
tempCache
.
match
(
request
);
await
contentCache
.
put
(
request
,
response
);
}
await
caches
.
delete
(
TEMP
);
// Save the manifest to make future upgrades efficient.
await
manifestCache
.
put
(
'manifest'
,
new
Response
(
JSON
.
stringify
(
RESOURCES
)));
// Claim client to enable caching on first launch
self
.
clients
.
claim
();
return
;
}
catch
(
err
)
{
// On an unhandled exception the state of the cache cannot be guaranteed.
console
.
error
(
'Failed to upgrade service worker: '
+
err
);
await
caches
.
delete
(
CACHE_NAME
);
await
caches
.
delete
(
TEMP
);
await
caches
.
delete
(
MANIFEST
);
}
}());
});
// The fetch handler redirects requests for RESOURCE files to the service
// worker cache.
self
.
addEventListener
(
"fetch"
,
(
event
)
=>
{
if
(
event
.
request
.
method
!==
'GET'
)
{
return
;
}
var
origin
=
self
.
location
.
origin
;
var
key
=
event
.
request
.
url
.
substring
(
origin
.
length
+
1
);
// Redirect URLs to the index.html
if
(
key
.
indexOf
(
'?v='
)
!=
-
1
)
{
key
=
key
.
split
(
'?v='
)[
0
];
}
if
(
event
.
request
.
url
==
origin
||
event
.
request
.
url
.
startsWith
(
origin
+
'/#'
)
||
key
==
''
)
{
key
=
'/'
;
}
// If the URL is not the RESOURCE list then return to signal that the
// browser should take over.
if
(!
RESOURCES
[
key
])
{
return
;
}
// If the URL is the index.html, perform an online-first request.
if
(
key
==
'/'
)
{
return
onlineFirst
(
event
);
}
event
.
respondWith
(
caches
.
open
(
CACHE_NAME
)
.
then
((
cache
)
=>
{
return
cache
.
match
(
event
.
request
).
then
((
response
)
=>
{
// Either respond with the cached resource, or perform a fetch and
// lazily populate the cache only if the resource was successfully fetched.
return
response
||
fetch
(
event
.
request
).
then
((
response
)
=>
{
if
(
response
&&
Boolean
(
response
.
ok
))
{
cache
.
put
(
event
.
request
,
response
.
clone
());
}
return
response
;
});
})
})
);
});
self
.
addEventListener
(
'message'
,
(
event
)
=>
{
// SkipWaiting can be used to immediately activate a waiting service worker.
// This will also require a page refresh triggered by the main worker.
if
(
event
.
data
===
'skipWaiting'
)
{
self
.
skipWaiting
();
return
;
}
if
(
event
.
data
===
'downloadOffline'
)
{
downloadOffline
();
return
;
}
});
// Download offline will check the RESOURCES for all files not in the cache
final
String
flutterServiceWorkerJsPath
=
globals
.
localFileSystem
.
path
.
join
(
// and populate them.
fileGeneratorsPath
,
async
function
downloadOffline
(
)
{
'js'
,
var
resources
=
[];
'flutter_service_worker.js'
,
var
contentCache
=
await
caches
.
open
(
CACHE_NAME
);
var
currentContent
=
{};
for
(
var
request
of
await
contentCache
.
keys
())
{
var
key
=
request
.
url
.
substring
(
origin
.
length
+
1
);
if
(
key
==
""
)
{
key
=
"/"
;
}
currentContent
[
key
]
=
true
;
}
for
(
var
resourceKey
of
Object
.
keys
(
RESOURCES
))
{
if
(!
currentContent
[
resourceKey
])
{
resources
.
push
(
resourceKey
);
}
}
return
contentCache
.
addAll
(
resources
);
}
// Attempt to download the resource online before falling back to
// the offline cache.
function
onlineFirst
(
event
)
{
return
event
.
respondWith
(
fetch
(
event
.
request
).
then
((
response
)
=>
{
return
caches
.
open
(
CACHE_NAME
).
then
((
cache
)
=>
{
cache
.
put
(
event
.
request
,
response
.
clone
());
return
response
;
});
}).
catch
((
error
)
=>
{
return
caches
.
open
(
CACHE_NAME
).
then
((
cache
)
=>
{
return
cache
.
match
(
event
.
request
).
then
((
response
)
=>
{
if
(
response
!=
null
)
{
return
response
;
}
throw
error
;
});
});
})
);
);
}
return
globals
.
localFileSystem
''';
.
file
(
flutterServiceWorkerJsPath
)
.
readAsStringSync
()
.
replaceAll
(
r'$$RESOURCES_MAP'
,
'{
${resources.entries.map((MapEntry<String, String> entry) => '"${entry.key}
": "
${entry.value}
"'
).
join
(
",
\n
"
)}}
',
)
.replaceAll(
r'
$
$CORE_LIST
',
'
[
$
{
coreBundle
.
map
((
String
file
)
=>
'"
$file
"'
).
join
(
',
\n
'
)}]
',
);
}
}
packages/flutter_tools/lib/src/web/file_generators/js/flutter.js
0 → 100644
View file @
d5dbcb70
// 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.
if
(
!
_flutter
)
{
var
_flutter
=
{};
}
_flutter
.
loader
=
null
;
(
function
()
{
"use strict"
;
const
baseUri
=
ensureTrailingSlash
(
getBaseURI
());
function
getBaseURI
()
{
const
base
=
document
.
querySelector
(
"base"
);
return
(
base
&&
base
.
getAttribute
(
"href"
))
||
""
;
}
function
ensureTrailingSlash
(
uri
)
{
if
(
uri
==
""
)
{
return
uri
;
}
return
uri
.
endsWith
(
"/"
)
?
uri
:
`
${
uri
}
/`
;
}
/**
* 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 the creation of a TrustedTypes `policy` that validates URLs based
* on an (optional) incoming array of RegExes.
*/
class
FlutterTrustedTypesPolicy
{
/**
* Constructs the policy.
* @param {[RegExp]} validPatterns the patterns to test URLs
* @param {String} policyName the policy name (optional)
*/
constructor
(
validPatterns
,
policyName
=
"flutter-js"
)
{
const
patterns
=
validPatterns
||
[
/
\.
dart
\.
js$/
,
/^flutter_service_worker.js$/
];
if
(
window
.
trustedTypes
)
{
this
.
policy
=
trustedTypes
.
createPolicy
(
policyName
,
{
createScriptURL
:
function
(
url
)
{
const
parsed
=
new
URL
(
url
,
window
.
location
);
const
file
=
parsed
.
pathname
.
split
(
"/"
).
pop
();
const
matches
=
patterns
.
some
((
pattern
)
=>
pattern
.
test
(
file
));
if
(
matches
)
{
return
parsed
.
toString
();
}
console
.
error
(
"URL rejected by TrustedTypes policy"
,
policyName
,
":"
,
url
,
"(download prevented)"
);
}
});
}
}
}
/**
* Handles loading/reloading Flutter's service worker, if configured.
*
* @see: https://developers.google.com/web/fundamentals/primers/service-workers
*/
class
FlutterServiceWorkerLoader
{
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy
(
policy
)
{
this
.
_ttPolicy
=
policy
;
}
/**
* 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
=
`
${
baseUri
}
flutter_service_worker.js?v=
${
serviceWorkerVersion
}
`
,
timeoutMillis
=
4000
,
}
=
settings
;
// Apply the TrustedTypes policy, if present.
let
url
=
serviceWorkerUrl
;
if
(
this
.
_ttPolicy
!=
null
)
{
url
=
this
.
_ttPolicy
.
createScriptURL
(
url
);
}
const
serviceWorkerActivation
=
navigator
.
serviceWorker
.
register
(
url
)
.
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
()
{
// Watchdog to prevent injecting the main entrypoint multiple times.
this
.
_scriptLoaded
=
false
;
}
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy
(
policy
)
{
this
.
_ttPolicy
=
policy
;
}
/**
* Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
* object when it's done.
*
* @param {*} options
* @returns {Promise | undefined} that will eventually resolve with an
* EngineInitializer, or will be rejected with the error caused by the loader.
* Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`.
*/
async
loadEntrypoint
(
options
)
{
const
{
entrypointUrl
=
`
${
baseUri
}
main.dart.js`
,
onEntrypointLoaded
}
=
options
||
{};
return
this
.
_loadEntrypoint
(
entrypointUrl
,
onEntrypointLoaded
);
}
/**
* Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded`
* function supplied by the user (if needed).
*
* 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
)
{
if
(
typeof
this
.
_didCreateEngineInitializerResolve
===
"function"
)
{
this
.
_didCreateEngineInitializerResolve
(
engineInitializer
);
// Remove the resolver after the first time, so Flutter Web can hot restart.
this
.
_didCreateEngineInitializerResolve
=
null
;
// Make the engine revert to "auto" initialization on hot restart.
delete
_flutter
.
loader
.
didCreateEngineInitializer
;
}
if
(
typeof
this
.
_onEntrypointLoaded
===
"function"
)
{
this
.
_onEntrypointLoaded
(
engineInitializer
);
}
}
/**
* 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
)
{
this
.
_scriptLoaded
=
true
;
const
scriptTag
=
this
.
_createScriptTag
(
entrypointUrl
);
if
(
useCallback
)
{
// Just inject the script tag, and return nothing; Flutter will call
// `didCreateEngineInitializer` when it's done.
console
.
debug
(
"Injecting <script> tag. Using callback."
);
this
.
_onEntrypointLoaded
=
onEntrypointLoaded
;
document
.
body
.
append
(
scriptTag
);
}
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
;
scriptTag
.
addEventListener
(
"error"
,
reject
);
document
.
body
.
append
(
scriptTag
);
});
}
}
}
/**
* Creates a script tag for the given URL.
* @param {string} url
* @returns {HTMLScriptElement}
*/
_createScriptTag
(
url
)
{
const
scriptTag
=
document
.
createElement
(
"script"
);
scriptTag
.
type
=
"application/javascript"
;
// Apply TrustedTypes validation, if available.
let
trustedUrl
=
url
;
if
(
this
.
_ttPolicy
!=
null
)
{
trustedUrl
=
this
.
_ttPolicy
.
createScriptURL
(
url
);
}
scriptTag
.
src
=
trustedUrl
;
return
scriptTag
;
}
}
/**
* The public interface of _flutter.loader. Exposes two methods:
* * loadEntrypoint (which coordinates the default Flutter web loading procedure)
* * 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
||
{};
// A Trusted Types policy that is going to be used by the loader.
const
flutterTT
=
new
FlutterTrustedTypesPolicy
();
// The FlutterServiceWorkerLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const
serviceWorkerLoader
=
new
FlutterServiceWorkerLoader
();
serviceWorkerLoader
.
setTrustedTypesPolicy
(
flutterTT
.
policy
);
await
serviceWorkerLoader
.
loadServiceWorker
(
serviceWorker
).
catch
(
e
=>
{
// Regardless of what happens with the injection of the SW, the show must go on
console
.
warn
(
"Exception while loading service worker:"
,
e
);
});
// The FlutterEntrypointLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const
entrypointLoader
=
new
FlutterEntrypointLoader
();
entrypointLoader
.
setTrustedTypesPolicy
(
flutterTT
.
policy
);
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
this
.
didCreateEngineInitializer
=
entrypointLoader
.
didCreateEngineInitializer
.
bind
(
entrypointLoader
);
return
entrypointLoader
.
loadEntrypoint
(
entrypoint
);
}
}
_flutter
.
loader
=
new
FlutterLoader
();
})();
packages/flutter_tools/lib/src/web/file_generators/js/flutter_service_worker.js
0 → 100644
View file @
d5dbcb70
'use strict'
;
const
MANIFEST
=
'flutter-app-manifest'
;
const
TEMP
=
'flutter-temp-cache'
;
const
CACHE_NAME
=
'flutter-app-cache'
;
const
RESOURCES
=
$$RESOURCES_MAP
;
// The application shell files that are downloaded before a service worker can
// start.
const
CORE
=
$$CORE_LIST
;
// During install, the TEMP cache is populated with the application shell files.
self
.
addEventListener
(
"install"
,
(
event
)
=>
{
self
.
skipWaiting
();
return
event
.
waitUntil
(
caches
.
open
(
TEMP
).
then
((
cache
)
=>
{
return
cache
.
addAll
(
CORE
.
map
((
value
)
=>
new
Request
(
value
,
{
'cache'
:
'reload'
})));
})
);
});
// During activate, the cache is populated with the temp files downloaded in
// install. If this service worker is upgrading from one with a saved
// MANIFEST, then use this to retain unchanged resource files.
self
.
addEventListener
(
"activate"
,
function
(
event
)
{
return
event
.
waitUntil
(
async
function
()
{
try
{
var
contentCache
=
await
caches
.
open
(
CACHE_NAME
);
var
tempCache
=
await
caches
.
open
(
TEMP
);
var
manifestCache
=
await
caches
.
open
(
MANIFEST
);
var
manifest
=
await
manifestCache
.
match
(
'manifest'
);
// When there is no prior manifest, clear the entire cache.
if
(
!
manifest
)
{
await
caches
.
delete
(
CACHE_NAME
);
contentCache
=
await
caches
.
open
(
CACHE_NAME
);
for
(
var
request
of
await
tempCache
.
keys
())
{
var
response
=
await
tempCache
.
match
(
request
);
await
contentCache
.
put
(
request
,
response
);
}
await
caches
.
delete
(
TEMP
);
// Save the manifest to make future upgrades efficient.
await
manifestCache
.
put
(
'manifest'
,
new
Response
(
JSON
.
stringify
(
RESOURCES
)));
// Claim client to enable caching on first launch
self
.
clients
.
claim
();
return
;
}
var
oldManifest
=
await
manifest
.
json
();
var
origin
=
self
.
location
.
origin
;
for
(
var
request
of
await
contentCache
.
keys
())
{
var
key
=
request
.
url
.
substring
(
origin
.
length
+
1
);
if
(
key
==
""
)
{
key
=
"/"
;
}
// If a resource from the old manifest is not in the new cache, or if
// the MD5 sum has changed, delete it. Otherwise the resource is left
// in the cache and can be reused by the new service worker.
if
(
!
RESOURCES
[
key
]
||
RESOURCES
[
key
]
!=
oldManifest
[
key
])
{
await
contentCache
.
delete
(
request
);
}
}
// Populate the cache with the app shell TEMP files, potentially overwriting
// cache files preserved above.
for
(
var
request
of
await
tempCache
.
keys
())
{
var
response
=
await
tempCache
.
match
(
request
);
await
contentCache
.
put
(
request
,
response
);
}
await
caches
.
delete
(
TEMP
);
// Save the manifest to make future upgrades efficient.
await
manifestCache
.
put
(
'manifest'
,
new
Response
(
JSON
.
stringify
(
RESOURCES
)));
// Claim client to enable caching on first launch
self
.
clients
.
claim
();
return
;
}
catch
(
err
)
{
// On an unhandled exception the state of the cache cannot be guaranteed.
console
.
error
(
'Failed to upgrade service worker: '
+
err
);
await
caches
.
delete
(
CACHE_NAME
);
await
caches
.
delete
(
TEMP
);
await
caches
.
delete
(
MANIFEST
);
}
}());
});
// The fetch handler redirects requests for RESOURCE files to the service
// worker cache.
self
.
addEventListener
(
"fetch"
,
(
event
)
=>
{
if
(
event
.
request
.
method
!==
'GET'
)
{
return
;
}
var
origin
=
self
.
location
.
origin
;
var
key
=
event
.
request
.
url
.
substring
(
origin
.
length
+
1
);
// Redirect URLs to the index.html
if
(
key
.
indexOf
(
'?v='
)
!=
-
1
)
{
key
=
key
.
split
(
'?v='
)[
0
];
}
if
(
event
.
request
.
url
==
origin
||
event
.
request
.
url
.
startsWith
(
origin
+
'/#'
)
||
key
==
''
)
{
key
=
'/'
;
}
// If the URL is not the RESOURCE list then return to signal that the
// browser should take over.
if
(
!
RESOURCES
[
key
])
{
return
;
}
// If the URL is the index.html, perform an online-first request.
if
(
key
==
'/'
)
{
return
onlineFirst
(
event
);
}
event
.
respondWith
(
caches
.
open
(
CACHE_NAME
)
.
then
((
cache
)
=>
{
return
cache
.
match
(
event
.
request
).
then
((
response
)
=>
{
// Either respond with the cached resource, or perform a fetch and
// lazily populate the cache only if the resource was successfully fetched.
return
response
||
fetch
(
event
.
request
).
then
((
response
)
=>
{
if
(
response
&&
Boolean
(
response
.
ok
))
{
cache
.
put
(
event
.
request
,
response
.
clone
());
}
return
response
;
});
})
})
);
});
self
.
addEventListener
(
'message'
,
(
event
)
=>
{
// SkipWaiting can be used to immediately activate a waiting service worker.
// This will also require a page refresh triggered by the main worker.
if
(
event
.
data
===
'skipWaiting'
)
{
self
.
skipWaiting
();
return
;
}
if
(
event
.
data
===
'downloadOffline'
)
{
downloadOffline
();
return
;
}
});
// Download offline will check the RESOURCES for all files not in the cache
// and populate them.
async
function
downloadOffline
()
{
var
resources
=
[];
var
contentCache
=
await
caches
.
open
(
CACHE_NAME
);
var
currentContent
=
{};
for
(
var
request
of
await
contentCache
.
keys
())
{
var
key
=
request
.
url
.
substring
(
origin
.
length
+
1
);
if
(
key
==
""
)
{
key
=
"/"
;
}
currentContent
[
key
]
=
true
;
}
for
(
var
resourceKey
of
Object
.
keys
(
RESOURCES
))
{
if
(
!
currentContent
[
resourceKey
])
{
resources
.
push
(
resourceKey
);
}
}
return
contentCache
.
addAll
(
resources
);
}
// Attempt to download the resource online before falling back to
// the offline cache.
function
onlineFirst
(
event
)
{
return
event
.
respondWith
(
fetch
(
event
.
request
).
then
((
response
)
=>
{
return
caches
.
open
(
CACHE_NAME
).
then
((
cache
)
=>
{
cache
.
put
(
event
.
request
,
response
.
clone
());
return
response
;
});
}).
catch
((
error
)
=>
{
return
caches
.
open
(
CACHE_NAME
).
then
((
cache
)
=>
{
return
cache
.
match
(
event
.
request
).
then
((
response
)
=>
{
if
(
response
!=
null
)
{
return
response
;
}
throw
error
;
});
});
})
);
}
packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
View file @
d5dbcb70
...
@@ -785,19 +785,40 @@ void main() {
...
@@ -785,19 +785,40 @@ void main() {
}));
}));
test
(
'Generated service worker is empty with none-strategy'
,
()
{
test
(
'Generated service worker is empty with none-strategy'
,
()
{
final
String
result
=
generateServiceWorker
(<
String
,
String
>{
'/foo'
:
'abcd'
},
<
String
>[],
serviceWorkerStrategy:
ServiceWorkerStrategy
.
none
);
final
String
fileGeneratorsPath
=
environment
.
artifacts
.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
final
String
result
=
generateServiceWorker
(
fileGeneratorsPath
,
<
String
,
String
>{
'/foo'
:
'abcd'
},
<
String
>[],
serviceWorkerStrategy:
ServiceWorkerStrategy
.
none
,
);
expect
(
result
,
''
);
expect
(
result
,
''
);
});
});
test
(
'Generated service worker correctly inlines file hashes'
,
()
{
test
(
'Generated service worker correctly inlines file hashes'
,
()
{
final
String
result
=
generateServiceWorker
(<
String
,
String
>{
'/foo'
:
'abcd'
},
<
String
>[],
serviceWorkerStrategy:
ServiceWorkerStrategy
.
offlineFirst
);
final
String
fileGeneratorsPath
=
environment
.
artifacts
.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
final
String
result
=
generateServiceWorker
(
fileGeneratorsPath
,
<
String
,
String
>{
'/foo'
:
'abcd'
},
<
String
>[],
serviceWorkerStrategy:
ServiceWorkerStrategy
.
offlineFirst
,
);
expect
(
result
,
contains
(
'{
\n
"/foo": "abcd"
\n
};'
));
expect
(
result
,
contains
(
'{
"/foo": "abcd"
};'
));
});
});
test
(
'Generated service worker includes core files'
,
()
{
test
(
'Generated service worker includes core files'
,
()
{
final
String
result
=
generateServiceWorker
(<
String
,
String
>{
'/foo'
:
'abcd'
},
<
String
>[
'foo'
,
'bar'
],
serviceWorkerStrategy:
ServiceWorkerStrategy
.
offlineFirst
);
final
String
fileGeneratorsPath
=
environment
.
artifacts
.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
final
String
result
=
generateServiceWorker
(
fileGeneratorsPath
,
<
String
,
String
>{
'/foo'
:
'abcd'
},
<
String
>[
'foo'
,
'bar'
],
serviceWorkerStrategy:
ServiceWorkerStrategy
.
offlineFirst
,
);
expect
(
result
,
contains
(
'"foo",
\n
"bar"'
));
expect
(
result
,
contains
(
'"foo",
\n
"bar"'
));
});
});
...
@@ -854,7 +875,10 @@ void main() {
...
@@ -854,7 +875,10 @@ void main() {
}));
}));
test
(
'flutter.js sanity checks'
,
()
{
test
(
'flutter.js sanity checks'
,
()
{
final
String
flutterJsContents
=
flutter_js
.
generateFlutterJsFile
();
final
String
fileGeneratorsPath
=
environment
.
artifacts
.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
final
String
flutterJsContents
=
flutter_js
.
generateFlutterJsFile
(
fileGeneratorsPath
);
expect
(
flutterJsContents
,
contains
(
'"use strict";'
));
expect
(
flutterJsContents
,
contains
(
'"use strict";'
));
expect
(
flutterJsContents
,
contains
(
'main.dart.js'
));
expect
(
flutterJsContents
,
contains
(
'main.dart.js'
));
expect
(
flutterJsContents
,
contains
(
'flutter_service_worker.js?v='
));
expect
(
flutterJsContents
,
contains
(
'flutter_service_worker.js?v='
));
...
@@ -873,8 +897,14 @@ void main() {
...
@@ -873,8 +897,14 @@ void main() {
await
WebBuiltInAssets
(
globals
.
fs
,
globals
.
cache
,
false
).
build
(
environment
);
await
WebBuiltInAssets
(
globals
.
fs
,
globals
.
cache
,
false
).
build
(
environment
);
// No caching of source maps.
// No caching of source maps.
expect
(
environment
.
outputDir
.
childFile
(
'flutter.js'
).
readAsStringSync
(),
final
String
fileGeneratorsPath
=
environment
.
artifacts
equals
(
flutter_js
.
generateFlutterJsFile
()));
.
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
final
String
flutterJsContents
=
flutter_js
.
generateFlutterJsFile
(
fileGeneratorsPath
);
expect
(
environment
.
outputDir
.
childFile
(
'flutter.js'
).
readAsStringSync
(),
equals
(
flutterJsContents
),
);
}));
}));
test
(
'wasm build copies and generates specific files'
,
()
=>
testbed
.
run
(()
async
{
test
(
'wasm build copies and generates specific files'
,
()
=>
testbed
.
run
(()
async
{
...
...
packages/flutter_tools/test/integration.shard/test_data/project.dart
View file @
d5dbcb70
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:file/file.dart'
;
import
'package:file/file.dart'
;
import
'package:flutter_tools/src/artifacts.dart'
;
import
'package:flutter_tools/src/web/file_generators/flutter_js.dart'
;
import
'package:flutter_tools/src/web/file_generators/flutter_js.dart'
;
import
'../test_utils.dart'
;
import
'../test_utils.dart'
;
...
@@ -59,9 +60,13 @@ abstract class Project {
...
@@ -59,9 +60,13 @@ abstract class Project {
}
}
deferredComponents
?.
setUpIn
(
dir
);
deferredComponents
?.
setUpIn
(
dir
);
final
String
fileGeneratorsPath
=
Artifacts
.
test
().
getArtifactPath
(
Artifact
.
flutterToolsFileGenerators
);
final
String
flutterJsContents
=
generateFlutterJsFile
(
fileGeneratorsPath
);
// Setup for different flutter web initializations
// Setup for different flutter web initializations
writeFile
(
fileSystem
.
path
.
join
(
dir
.
path
,
'web'
,
'index.html'
),
indexHtml
);
writeFile
(
fileSystem
.
path
.
join
(
dir
.
path
,
'web'
,
'index.html'
),
indexHtml
);
writeFile
(
fileSystem
.
path
.
join
(
dir
.
path
,
'web'
,
'flutter.js'
),
generateFlutterJsFile
()
);
writeFile
(
fileSystem
.
path
.
join
(
dir
.
path
,
'web'
,
'flutter.js'
),
flutterJsContents
);
writeFile
(
fileSystem
.
path
.
join
(
dir
.
path
,
'web'
,
'flutter_service_worker.js'
),
''
);
writeFile
(
fileSystem
.
path
.
join
(
dir
.
path
,
'web'
,
'flutter_service_worker.js'
),
''
);
writePackages
(
dir
.
path
);
writePackages
(
dir
.
path
);
await
getPackages
(
dir
.
path
);
await
getPackages
(
dir
.
path
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment