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
bee95b69
Unverified
Commit
bee95b69
authored
Apr 12, 2022
by
David Iglesias
Committed by
GitHub
Apr 12, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter.js] Wait for reg.update, then activate sw (if not active yet). (#101464)
parent
fa48aed7
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
312 additions
and
29 deletions
+312
-29
service_worker_test.dart
dev/bots/service_worker_test.dart
+115
-7
test.dart
dev/bots/test.dart
+3
-1
index_with_flutterjs.html
dev/integration_tests/web/web/index_with_flutterjs.html
+39
-0
index_with_flutterjs_short.html
...integration_tests/web/web/index_with_flutterjs_short.html
+37
-0
index_without_flutterjs.html
dev/integration_tests/web/web/index_without_flutterjs.html
+86
-0
flutter_js.dart
packages/flutter_tools/lib/src/web/flutter_js.dart
+32
-21
No files found.
dev/bots/service_worker_test.dart
View file @
bee95b69
...
...
@@ -17,13 +17,22 @@ final String _bat = Platform.isWindows ? '.bat' : '';
final
String
_flutterRoot
=
path
.
dirname
(
path
.
dirname
(
path
.
dirname
(
path
.
fromUri
(
Platform
.
script
))));
final
String
_flutter
=
path
.
join
(
_flutterRoot
,
'bin'
,
'flutter
$_bat
'
);
final
String
_testAppDirectory
=
path
.
join
(
_flutterRoot
,
'dev'
,
'integration_tests'
,
'web'
);
final
String
_testAppWebDirectory
=
path
.
join
(
_testAppDirectory
,
'web'
);
final
String
_appBuildDirectory
=
path
.
join
(
_testAppDirectory
,
'build'
,
'web'
);
final
String
_target
=
path
.
join
(
'lib'
,
'service_worker_test.dart'
);
final
String
_targetPath
=
path
.
join
(
_testAppDirectory
,
_target
);
enum
ServiceWorkerTestType
{
withoutFlutterJs
,
withFlutterJs
,
withFlutterJsShort
,
}
// Run a web service worker test as a standalone Dart program.
Future
<
void
>
main
()
async
{
await
runWebServiceWorkerTest
(
headless:
false
);
await
runWebServiceWorkerTest
(
headless:
false
,
testType:
ServiceWorkerTestType
.
withFlutterJs
);
await
runWebServiceWorkerTest
(
headless:
false
,
testType:
ServiceWorkerTestType
.
withoutFlutterJs
);
await
runWebServiceWorkerTest
(
headless:
false
,
testType:
ServiceWorkerTestType
.
withFlutterJsShort
);
}
Future
<
void
>
_setAppVersion
(
int
version
)
async
{
...
...
@@ -36,13 +45,37 @@ Future<void> _setAppVersion(int version) async {
);
}
Future
<
void
>
_rebuildApp
({
required
int
version
})
async
{
String
_testTypeToIndexFile
(
ServiceWorkerTestType
type
)
{
late
String
indexFile
;
switch
(
type
)
{
case
ServiceWorkerTestType
.
withFlutterJs
:
indexFile
=
'index_with_flutterjs.html'
;
break
;
case
ServiceWorkerTestType
.
withoutFlutterJs
:
indexFile
=
'index_without_flutterjs.html'
;
break
;
case
ServiceWorkerTestType
.
withFlutterJsShort
:
indexFile
=
'index_with_flutterjs_short.html'
;
break
;
}
return
indexFile
;
}
Future
<
void
>
_rebuildApp
({
required
int
version
,
required
ServiceWorkerTestType
testType
})
async
{
await
_setAppVersion
(
version
);
await
runCommand
(
_flutter
,
<
String
>[
'clean'
],
workingDirectory:
_testAppDirectory
,
);
await
runCommand
(
'cp'
,
<
String
>[
_testTypeToIndexFile
(
testType
),
'index.html'
,
],
workingDirectory:
_testAppWebDirectory
,
);
await
runCommand
(
_flutter
,
<
String
>[
'build'
,
'web'
,
'--profile'
,
'-t'
,
_target
],
...
...
@@ -69,9 +102,8 @@ void expect(Object? actual, Object? expected) {
Future
<
void
>
runWebServiceWorkerTest
({
required
bool
headless
,
required
ServiceWorkerTestType
testType
,
})
async
{
await
_rebuildApp
(
version:
1
);
final
Map
<
String
,
int
>
requestedPathCounts
=
<
String
,
int
>{};
void
expectRequestCounts
(
Map
<
String
,
int
>
expectedCounts
)
{
expect
(
requestedPathCounts
,
expectedCounts
);
...
...
@@ -124,10 +156,64 @@ Future<void> runWebServiceWorkerTest({
);
}
// Preserve old index.html as index_og.html so we can restore it later for other tests
await
runCommand
(
'mv'
,
<
String
>[
'index.html'
,
'index_og.html'
,
],
workingDirectory:
_testAppWebDirectory
,
);
final
bool
shouldExpectFlutterJs
=
testType
!=
ServiceWorkerTestType
.
withoutFlutterJs
;
print
(
'BEGIN runWebServiceWorkerTest(headless:
$headless
, testType:
$testType
)
\n
'
);
try
{
/////
// Attempt to load a different version of the service worker!
/////
await
_rebuildApp
(
version:
1
,
testType:
testType
);
print
(
'Call update() on the current web worker'
);
await
startAppServer
(
cacheControl:
'max-age=0'
);
await
waitForAppToLoad
(<
String
,
int
>
{
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
1
,
'CLOSE'
:
1
,
});
expect
(
reportedVersion
,
'1'
);
reportedVersion
=
null
;
await
server
!.
chrome
.
reloadPage
(
ignoreCache:
true
);
await
waitForAppToLoad
(<
String
,
int
>
{
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
2
,
'CLOSE'
:
2
,
});
expect
(
reportedVersion
,
'1'
);
reportedVersion
=
null
;
await
_rebuildApp
(
version:
2
,
testType:
testType
);
await
server
!.
chrome
.
reloadPage
(
ignoreCache:
true
);
await
waitForAppToLoad
(<
String
,
int
>{
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
3
,
'CLOSE'
:
3
,
});
expect
(
reportedVersion
,
'2'
);
reportedVersion
=
null
;
requestedPathCounts
.
clear
();
await
server
!.
stop
();
//////////////////////////////////////////////////////
// Caching server
//////////////////////////////////////////////////////
await
_rebuildApp
(
version:
1
,
testType:
testType
);
print
(
'With cache: test first page load'
);
await
startAppServer
(
cacheControl:
'max-age=3600'
);
await
waitForAppToLoad
(<
String
,
int
>{
...
...
@@ -140,6 +226,8 @@ Future<void> runWebServiceWorkerTest({
// once by the initial page load, and once by the service worker.
// Other resources are loaded once only by the service worker.
'index.html'
:
2
,
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
1
,
'main.dart.js'
:
1
,
'flutter_service_worker.js'
:
1
,
'assets/FontManifest.json'
:
1
,
...
...
@@ -171,7 +259,7 @@ Future<void> runWebServiceWorkerTest({
reportedVersion
=
null
;
print
(
'With cache: test page reload after rebuild'
);
await
_rebuildApp
(
version:
2
);
await
_rebuildApp
(
version:
2
,
testType:
testType
);
// Since we're caching, we need to ignore cache when reloading the page.
await
server
!.
chrome
.
reloadPage
(
ignoreCache:
true
);
...
...
@@ -181,6 +269,8 @@ Future<void> runWebServiceWorkerTest({
});
expectRequestCounts
(<
String
,
int
>{
'index.html'
:
2
,
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
1
,
'flutter_service_worker.js'
:
2
,
'main.dart.js'
:
1
,
'assets/NOTICES'
:
1
,
...
...
@@ -200,7 +290,7 @@ Future<void> runWebServiceWorkerTest({
// Non-caching server
//////////////////////////////////////////////////////
print
(
'No cache: test first page load'
);
await
_rebuildApp
(
version:
3
);
await
_rebuildApp
(
version:
3
,
testType:
testType
);
await
startAppServer
(
cacheControl:
'max-age=0'
);
await
waitForAppToLoad
(<
String
,
int
>{
'CLOSE'
:
1
,
...
...
@@ -209,6 +299,8 @@ Future<void> runWebServiceWorkerTest({
expectRequestCounts
(<
String
,
int
>{
'index.html'
:
2
,
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
1
,
// We still download some resources multiple times if the server is non-caching.
'main.dart.js'
:
2
,
'assets/FontManifest.json'
:
2
,
...
...
@@ -231,10 +323,14 @@ Future<void> runWebServiceWorkerTest({
await
server
!.
chrome
.
reloadPage
();
await
waitForAppToLoad
(<
String
,
int
>{
'CLOSE'
:
1
,
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
1
,
'flutter_service_worker.js'
:
1
,
});
expectRequestCounts
(<
String
,
int
>{
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
1
,
'flutter_service_worker.js'
:
1
,
'CLOSE'
:
1
,
if
(!
headless
)
...
...
@@ -244,7 +340,7 @@ Future<void> runWebServiceWorkerTest({
reportedVersion
=
null
;
print
(
'No cache: test page reload after rebuild'
);
await
_rebuildApp
(
version:
4
);
await
_rebuildApp
(
version:
4
,
testType:
testType
);
// TODO(yjbanov): when running Chrome with DevTools protocol, for some
// reason a hard refresh is still required. This works without a hard
...
...
@@ -258,6 +354,8 @@ Future<void> runWebServiceWorkerTest({
});
expectRequestCounts
(<
String
,
int
>{
'index.html'
:
2
,
if
(
shouldExpectFlutterJs
)
'flutter.js'
:
1
,
'flutter_service_worker.js'
:
2
,
'main.dart.js'
:
2
,
'assets/NOTICES'
:
1
,
...
...
@@ -274,7 +372,17 @@ Future<void> runWebServiceWorkerTest({
expect
(
reportedVersion
,
'4'
);
reportedVersion
=
null
;
}
finally
{
await
runCommand
(
'mv'
,
<
String
>[
'index_og.html'
,
'index.html'
,
],
workingDirectory:
_testAppWebDirectory
,
);
await
_setAppVersion
(
1
);
await
server
?.
stop
();
}
print
(
'END runWebServiceWorkerTest(headless:
$headless
, testType:
$testType
)
\n
'
);
}
dev/bots/test.dart
View file @
bee95b69
...
...
@@ -1069,7 +1069,9 @@ Future<void> _runWebLongRunningTests() async {
()
=>
_runGalleryE2eWebTest
(
'profile'
,
canvasKit:
true
),
()
=>
_runGalleryE2eWebTest
(
'release'
),
()
=>
_runGalleryE2eWebTest
(
'release'
,
canvasKit:
true
),
()
=>
runWebServiceWorkerTest
(
headless:
true
),
()
=>
runWebServiceWorkerTest
(
headless:
true
,
testType:
ServiceWorkerTestType
.
withoutFlutterJs
),
()
=>
runWebServiceWorkerTest
(
headless:
true
,
testType:
ServiceWorkerTestType
.
withFlutterJs
),
()
=>
runWebServiceWorkerTest
(
headless:
true
,
testType:
ServiceWorkerTestType
.
withFlutterJsShort
),
()
=>
_runWebStackTraceTest
(
'profile'
,
'lib/stack_trace.dart'
),
()
=>
_runWebStackTraceTest
(
'release'
,
'lib/stack_trace.dart'
),
()
=>
_runWebStackTraceTest
(
'profile'
,
'lib/framework_stack_trace.dart'
),
...
...
dev/integration_tests/web/web/index_with_flutterjs.html
0 → 100644
View file @
bee95b69
<!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>
Web Test
</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
({
serviceWorker
:
{
serviceWorkerVersion
:
serviceWorkerVersion
,
}
}).
then
(
function
(
engineInitializer
)
{
return
engineInitializer
.
initializeEngine
();
}).
then
(
function
(
appRunner
)
{
return
appRunner
.
runApp
();
});
});
</script>
</body>
</html>
dev/integration_tests/web/web/index_with_flutterjs_short.html
0 → 100644
View file @
bee95b69
<!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>
Web Test
</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
({
serviceWorker
:
{
serviceWorkerVersion
:
serviceWorkerVersion
,
}
}).
then
(
function
(
engineInitializer
)
{
return
engineInitializer
.
autoStart
();
});
});
</script>
</body>
</html>
dev/integration_tests/web/web/index_without_flutterjs.html
0 → 100644
View file @
bee95b69
<!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>
Web Test
</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"
>
</head>
<body>
<!-- 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 -->
<script>
var
serviceWorkerVersion
=
null
;
var
scriptLoaded
=
false
;
function
loadMainDartJs
()
{
if
(
scriptLoaded
)
{
return
;
}
scriptLoaded
=
true
;
var
scriptTag
=
document
.
createElement
(
'script'
);
scriptTag
.
src
=
'main.dart.js'
;
scriptTag
.
type
=
'application/javascript'
;
document
.
body
.
append
(
scriptTag
);
}
if
(
'serviceWorker'
in
navigator
)
{
// Service workers are supported. Use them.
window
.
addEventListener
(
'load'
,
function
()
{
// Wait for registration to finish before dropping the
<
script
>
tag
.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var
serviceWorkerUrl
=
'flutter_service_worker.js?v='
+
serviceWorkerVersion
;
navigator
.
serviceWorker
.
register
(
serviceWorkerUrl
)
.
then
((
reg
)
=>
{
function
waitForActivation
(
serviceWorker
)
{
serviceWorker
.
addEventListener
(
'statechange'
,
()
=>
{
if
(
serviceWorker
.
state
==
'activated'
)
{
console
.
log
(
'Installed new service worker.'
);
loadMainDartJs
();
}
});
}
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.
waitForActivation
(
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.
console
.
log
(
'New service worker available.'
);
reg
.
update
();
waitForActivation
(
reg
.
installing
);
}
else
{
// Existing service worker is still good.
console
.
log
(
'Loading app from service worker.'
);
loadMainDartJs
();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint
<
script
>
tag
.
setTimeout
(()
=>
{
if
(
!
scriptLoaded
)
{
console
.
warn
(
'Failed to load app from service worker. Falling back to plain <script> tag.'
,
);
loadMainDartJs
();
}
},
4000
);
});
}
else
{
// Service workers not supported. Just drop the
<
script
>
tag
.
loadMainDartJs
();
}
</script>
</body>
</html>
packages/flutter_tools/lib/src/web/flutter_js.dart
View file @
bee95b69
...
...
@@ -31,7 +31,7 @@ _flutter.loader = null;
// we support. In the meantime, we use the "revealing module" pattern.
// Watchdog to prevent injecting the main entrypoint multiple times.
_scriptLoaded =
false
;
_scriptLoaded =
null
;
// Resolver for the pending promise returned by loadEntrypoint.
_didCreateEngineInitializerResolve = null;
...
...
@@ -61,31 +61,38 @@ _flutter.loader = null;
console.warn("Do not call didCreateEngineInitializer by hand. Start with loadEntrypoint instead.");
}
this._didCreateEngineInitializerResolve(engineInitializer);
// Remove this method after it'
s
done
,
so
Flutter
Web
can
hot
restart
.
delete
this
.
didCreateEngineInitializer
;
}).
bind
(
this
);
_loadEntrypoint
(
entrypointUrl
)
{
if (this._scriptLoaded) {
return null;
if
(!
this
.
_scriptLoaded
)
{
this
.
_scriptLoaded
=
new
Promise
((
resolve
,
reject
)
=>
{
let
scriptTag
=
document
.
createElement
(
"script"
);
scriptTag
.
src
=
entrypointUrl
;
scriptTag
.
type
=
"application/javascript"
;
this
.
_didCreateEngineInitializerResolve
=
resolve
;
// Cache the resolve, so it can be called from Flutter.
scriptTag
.
addEventListener
(
"error"
,
reject
);
document
.
body
.
append
(
scriptTag
);
});
}
this._scriptLoaded = true;
return new Promise((resolve, reject) => {
let scriptTag = document.createElement("script");
scriptTag.src = entrypointUrl;
scriptTag.type = "application/javascript";
this._didCreateEngineInitializerResolve = resolve; // Cache the resolve, so it can be called from Flutter.
scriptTag.addEventListener("error", reject);
document.body.append(scriptTag);
});
return
this
.
_scriptLoaded
;
}
_waitForServiceWorkerActivation
(
serviceWorker
,
entrypointUrl
)
{
if (!serviceWorker) return;
if
(!
serviceWorker
||
serviceWorker
.
state
==
"activated"
)
{
if
(!
serviceWorker
)
{
console
.
warn
(
"Cannot activate a null service worker. Falling back to plain <script> tag."
);
}
else
{
console
.
debug
(
"Service worker already active."
);
}
return
this
.
_loadEntrypoint
(
entrypointUrl
);
}
return
new
Promise
((
resolve
,
_
)
=>
{
serviceWorker
.
addEventListener
(
"statechange"
,
()
=>
{
if
(
serviceWorker
.
state
==
"activated"
)
{
console.
lo
g("Installed new service worker.");
console
.
debu
g
(
"Installed new service worker."
);
resolve
(
this
.
_loadEntrypoint
(
entrypointUrl
));
}
});
...
...
@@ -103,22 +110,26 @@ _flutter.loader = null;
timeoutMillis
=
4000
,
}
=
serviceWorkerOptions
;
var
serviceWorkerUrl = "flutter_service_worker.js?v=" + serviceWorkerVersion;
let
serviceWorkerUrl
=
"flutter_service_worker.js?v="
+
serviceWorkerVersion
;
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.
return this._waitForServiceWorkerActivation(reg.installing || reg.waiting, entrypointUrl);
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.log("New service worker available.");
reg.update();
return this._waitForServiceWorkerActivation(reg.installing, entrypointUrl);
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.
lo
g("Loading app from service worker.");
console
.
debu
g
(
"Loading app from service worker."
);
return
this
.
_loadEntrypoint
(
entrypointUrl
);
}
});
...
...
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