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
e7953b3b
Unverified
Commit
e7953b3b
authored
Feb 11, 2021
by
Yegor
Committed by
GitHub
Feb 11, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[web] new service worker loading mechanism (#75535)
parent
78ce11d7
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
491 additions
and
140 deletions
+491
-140
browser.dart
dev/bots/browser.dart
+54
-0
service_worker_test.dart
dev/bots/service_worker_test.dart
+240
-116
test.dart
dev/bots/test.dart
+2
-0
browser.dart
dev/devicelab/lib/framework/browser.dart
+4
-0
index.html
dev/integration_tests/flutter_gallery/web/index.html
+59
-3
service_worker_test.dart
dev/integration_tests/web/lib/service_worker_test.dart
+4
-12
index.html
dev/integration_tests/web/web/index.html
+62
-2
web.dart
packages/flutter_tools/lib/src/build_system/targets/web.dart
+7
-1
index.html.tmpl
packages/flutter_tools/templates/app/web/index.html.tmpl
+59
-6
No files found.
dev/bots/browser.dart
View file @
e7953b3b
...
@@ -57,3 +57,57 @@ Future<String> evalTestAppInChrome({
...
@@ -57,3 +57,57 @@ Future<String> evalTestAppInChrome({
await
server
?.
close
();
await
server
?.
close
();
}
}
}
}
typedef
ServerRequestListener
=
void
Function
(
Request
);
class
AppServer
{
AppServer
.
_
(
this
.
_server
,
this
.
chrome
,
this
.
onChromeError
);
static
Future
<
AppServer
>
start
({
@required
String
appUrl
,
@required
String
appDirectory
,
@required
String
cacheControl
,
int
serverPort
=
8080
,
int
browserDebugPort
=
8081
,
bool
headless
=
true
,
List
<
Handler
>
additionalRequestHandlers
,
})
async
{
io
.
HttpServer
server
;
Chrome
chrome
;
server
=
await
io
.
HttpServer
.
bind
(
'localhost'
,
serverPort
);
final
Handler
staticHandler
=
createStaticHandler
(
appDirectory
,
defaultDocument:
'index.html'
);
Cascade
cascade
=
Cascade
();
if
(
additionalRequestHandlers
!=
null
)
{
for
(
final
Handler
handler
in
additionalRequestHandlers
)
{
cascade
=
cascade
.
add
(
handler
);
}
}
cascade
=
cascade
.
add
((
Request
request
)
async
{
final
Response
response
=
await
staticHandler
(
request
);
return
response
.
change
(
headers:
<
String
,
Object
>{
'cache-control'
:
cacheControl
,
});
});
shelf_io
.
serveRequests
(
server
,
cascade
.
handler
);
final
io
.
Directory
userDataDirectory
=
io
.
Directory
.
systemTemp
.
createTempSync
(
'chrome_user_data_'
);
final
Completer
<
String
>
chromeErrorCompleter
=
Completer
<
String
>();
chrome
=
await
Chrome
.
launch
(
ChromeOptions
(
headless:
headless
,
debugPort:
browserDebugPort
,
url:
appUrl
,
userDataDirectory:
userDataDirectory
.
path
,
windowHeight:
1024
,
windowWidth:
1024
,
),
onError:
chromeErrorCompleter
.
complete
);
return
AppServer
.
_
(
server
,
chrome
,
chromeErrorCompleter
.
future
);
}
final
Future
<
String
>
onChromeError
;
final
io
.
HttpServer
_server
;
final
Chrome
chrome
;
Future
<
void
>
stop
()
async
{
chrome
?.
stop
();
await
_server
?.
close
();
}
}
dev/bots/service_worker_test.dart
View file @
e7953b3b
...
@@ -6,138 +6,262 @@ import 'dart:async';
...
@@ -6,138 +6,262 @@ import 'dart:async';
import
'dart:io'
;
import
'dart:io'
;
import
'package:path/path.dart'
as
path
;
import
'package:path/path.dart'
as
path
;
import
'package:flutter_devicelab/framework/browser.dart'
;
import
'package:meta/meta.dart'
;
import
'package:meta/meta.dart'
;
import
'package:shelf/shelf.dart'
;
import
'package:shelf/shelf.dart'
;
import
'package:shelf_static/shelf_static.dart'
;
import
'package:shelf/shelf_io.dart'
as
shelf_io
;
final
String
bat
=
Platform
.
isWindows
?
'.bat'
:
'
'
;
import
'browser.dart
'
;
final
String
flutterRoot
=
path
.
dirname
(
path
.
dirname
(
path
.
dirname
(
path
.
fromUri
(
Platform
.
script
))))
;
import
'run_command.dart'
;
final
String
flutter
=
path
.
join
(
flutterRoot
,
'bin'
,
'flutter
$bat
'
)
;
import
'test/common.dart'
;
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
_appBuildDirectory
=
path
.
join
(
_testAppDirectory
,
'build'
,
'web'
);
final
String
_target
=
path
.
join
(
'lib'
,
'service_worker_test.dart'
);
final
String
_targetPath
=
path
.
join
(
_testAppDirectory
,
_target
);
// Run a web service worker test. The expectations are currently stored here
// Run a web service worker test as a standalone Dart program.
// instead of in the application. This is not run on CI due to the requirement
// of having a headful chrome instance.
Future
<
void
>
main
()
async
{
Future
<
void
>
main
()
async
{
await
_runWebServiceWorkerTest
(
'lib/service_worker_test.dart'
);
await
runWebServiceWorkerTest
(
headless:
false
);
}
}
Future
<
void
>
_runWebServiceWorkerTest
(
String
target
,
{
Future
<
void
>
_setAppVersion
(
int
version
)
async
{
List
<
String
>
additionalArguments
=
const
<
String
>[],
final
File
targetFile
=
File
(
_targetPath
);
})
async
{
await
targetFile
.
writeAsString
(
final
String
testAppDirectory
=
path
.
join
(
flutterRoot
,
'dev'
,
'integration_tests'
,
'web'
);
(
await
targetFile
.
readAsString
()).
replaceFirst
(
final
String
appBuildDirectory
=
path
.
join
(
testAppDirectory
,
'build'
,
'web'
);
RegExp
(
r'CLOSE\?version=\d+'
),
'CLOSE?version=
$version
'
,
)
);
}
// Build the app.
Future
<
void
>
_rebuildApp
({
@required
int
version
})
async
{
await
Process
.
run
(
await
_setAppVersion
(
version
);
flutter
,
await
runCommand
(
_flutter
,
<
String
>[
'clean'
],
<
String
>[
'clean'
],
workingDirectory:
testAppDirectory
,
workingDirectory:
_
testAppDirectory
,
);
);
await
Process
.
run
(
await
runCommand
(
flutter
,
_flutter
,
<
String
>[
<
String
>[
'build'
,
'web'
,
'--profile'
,
'-t'
,
_target
],
'build'
,
workingDirectory:
_testAppDirectory
,
'web'
,
'--release'
,
...
additionalArguments
,
'-t'
,
target
,
],
workingDirectory:
testAppDirectory
,
environment:
<
String
,
String
>{
environment:
<
String
,
String
>{
'FLUTTER_WEB'
:
'true'
,
'FLUTTER_WEB'
:
'true'
,
},
},
);
);
final
List
<
Uri
>
requests
=
<
Uri
>[];
}
final
List
<
Map
<
String
,
String
>>
headers
=
<
Map
<
String
,
String
>>[];
await
runRecordingServer
(
Future
<
void
>
runWebServiceWorkerTest
({
appUrl:
'http://localhost:8080/'
,
@required
bool
headless
,
appDirectory:
appBuildDirectory
,
})
async
{
requests:
requests
,
test
(
'flutter_service_worker.js'
,
()
async
{
headers:
headers
,
await
_rebuildApp
(
version:
1
);
browserDebugPort:
null
,
);
final
List
<
String
>
requestedPaths
=
requests
.
map
((
Uri
uri
)
=>
uri
.
toString
()).
toList
();
final
Map
<
String
,
int
>
requestedPathCounts
=
<
String
,
int
>{};
final
List
<
String
>
expectedPaths
=
<
String
>[
void
expectRequestCounts
(
Map
<
String
,
int
>
expectedCounts
)
{
// Initial page load
expect
(
requestedPathCounts
,
expectedCounts
);
''
,
requestedPathCounts
.
clear
();
'main.dart.js'
,
'assets/FontManifest.json'
,
'flutter_service_worker.js'
,
'manifest.json'
,
'favicon.ico'
,
// Service worker install.
'main.dart.js'
,
'index.html'
,
'assets/LICENSE'
,
'assets/AssetManifest.json'
,
'assets/FontManifest.json'
,
''
,
// Second page load all cached.
];
print
(
'requests:
$requestedPaths
'
);
// The exact order isn't important or deterministic.
for
(
final
String
path
in
requestedPaths
)
{
if
(!
expectedPaths
.
remove
(
path
))
{
print
(
'unexpected service worker request:
$path
'
);
exit
(
1
);
}
}
AppServer
server
;
Future
<
void
>
waitForAppToLoad
(
Map
<
String
,
int
>
waitForCounts
)
async
{
print
(
'Waiting for app to load
$waitForCounts
'
);
await
Future
.
any
(<
Future
<
void
>>[
()
async
{
while
(!
waitForCounts
.
entries
.
every
((
MapEntry
<
String
,
int
>
entry
)
=>
(
requestedPathCounts
[
entry
.
key
]
??
0
)
>=
entry
.
value
))
{
await
Future
<
void
>.
delayed
(
const
Duration
(
milliseconds:
100
));
}
}
if
(
expectedPaths
.
isNotEmpty
)
{
}(),
print
(
'Missing service worker requests from expected paths:
$expectedPaths
'
);
server
.
onChromeError
.
then
((
String
error
)
{
exit
(
1
);
throw
Exception
(
'Chrome error:
$error
'
);
}),
]);
}
}
}
/// This server runs a release web application and verifies that the service worker
String
reportedVersion
;
/// caches files correctly, by checking the request resources over HTTP.
///
Future
<
void
>
startAppServer
({
/// When it receives a request for `CLOSE` the server will be torn down.
@required
String
cacheControl
,
///
})
async
{
/// Expects a path to the `build/web` directory produced from `flutter build web`.
server
=
await
AppServer
.
start
(
Future
<
void
>
runRecordingServer
({
headless:
headless
,
@required
String
appUrl
,
cacheControl:
cacheControl
,
@required
String
appDirectory
,
appUrl:
'http://localhost:8080/index.html'
,
@required
List
<
Uri
>
requests
,
appDirectory:
_appBuildDirectory
,
@required
List
<
Map
<
String
,
String
>>
headers
,
additionalRequestHandlers:
<
Handler
>[
int
serverPort
=
8080
,
(
Request
request
)
{
int
browserDebugPort
=
8081
,
final
String
requestedPath
=
request
.
url
.
path
;
})
async
{
requestedPathCounts
.
putIfAbsent
(
requestedPath
,
()
=>
0
);
Chrome
chrome
;
requestedPathCounts
[
requestedPath
]
+=
1
;
HttpServer
server
;
if
(
requestedPath
==
'CLOSE'
)
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
reportedVersion
=
request
.
url
.
queryParameters
[
'version'
];
Directory
userDataDirectory
;
return
Response
.
ok
(
'OK'
);
try
{
server
=
await
HttpServer
.
bind
(
'localhost'
,
serverPort
);
final
Cascade
cascade
=
Cascade
()
.
add
((
Request
request
)
async
{
if
(
request
.
url
.
toString
().
contains
(
'CLOSE'
))
{
completer
.
complete
();
return
Response
.
notFound
(
''
);
}
}
requests
.
add
(
request
.
url
);
headers
.
add
(
request
.
headers
);
return
Response
.
notFound
(
''
);
return
Response
.
notFound
(
''
);
})
},
.
add
(
createStaticHandler
(
appDirectory
,
defaultDocument:
'index.html'
));
],
shelf_io
.
serveRequests
(
server
,
cascade
.
handler
);
);
userDataDirectory
=
Directory
.
systemTemp
.
createTempSync
(
'chrome_user_data_'
);
}
chrome
=
await
Chrome
.
launch
(
ChromeOptions
(
headless:
false
,
try
{
debugPort:
browserDebugPort
,
//////////////////////////////////////////////////////
url:
appUrl
,
// Caching server
userDataDirectory:
userDataDirectory
.
path
,
//////////////////////////////////////////////////////
windowHeight:
500
,
print
(
'With cache: test first page load'
);
windowWidth:
500
,
await
startAppServer
(
cacheControl:
'max-age=3600'
);
),
onError:
completer
.
completeError
);
await
waitForAppToLoad
(<
String
,
int
>{
await
completer
.
future
;
'CLOSE'
:
1
,
'flutter_service_worker.js'
:
1
,
});
expectRequestCounts
(<
String
,
int
>{
''
:
1
,
// Even though the server is caching index.html is downloaded twice,
// 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
,
'main.dart.js'
:
1
,
'flutter_service_worker.js'
:
1
,
'assets/FontManifest.json'
:
1
,
'assets/NOTICES'
:
1
,
'assets/AssetManifest.json'
:
1
,
'CLOSE'
:
1
,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
if
(!
headless
)
...<
String
,
int
>{
'manifest.json'
:
1
,
'favicon.ico'
:
1
,
}
});
expect
(
reportedVersion
,
'1'
);
reportedVersion
=
null
;
print
(
'With cache: test page reload'
);
await
server
.
chrome
.
reloadPage
();
await
waitForAppToLoad
(<
String
,
int
>{
'CLOSE'
:
1
,
'flutter_service_worker.js'
:
1
,
});
expectRequestCounts
(<
String
,
int
>{
'flutter_service_worker.js'
:
1
,
'CLOSE'
:
1
,
});
expect
(
reportedVersion
,
'1'
);
reportedVersion
=
null
;
print
(
'With cache: test page reload after rebuild'
);
await
_rebuildApp
(
version:
2
);
// Since we're caching, we need to ignore cache when reloading the page.
await
server
.
chrome
.
reloadPage
(
ignoreCache:
true
);
await
waitForAppToLoad
(<
String
,
int
>{
'CLOSE'
:
1
,
'flutter_service_worker.js'
:
2
,
});
expectRequestCounts
(<
String
,
int
>{
'index.html'
:
2
,
'flutter_service_worker.js'
:
2
,
''
:
1
,
'main.dart.js'
:
1
,
'assets/NOTICES'
:
1
,
'assets/AssetManifest.json'
:
1
,
'assets/FontManifest.json'
:
1
,
'CLOSE'
:
1
,
if
(!
headless
)
'favicon.ico'
:
1
,
});
expect
(
reportedVersion
,
'2'
);
reportedVersion
=
null
;
await
server
.
stop
();
//////////////////////////////////////////////////////
// Non-caching server
//////////////////////////////////////////////////////
print
(
'No cache: test first page load'
);
await
_rebuildApp
(
version:
3
);
await
startAppServer
(
cacheControl:
'max-age=0'
);
await
waitForAppToLoad
(<
String
,
int
>{
'CLOSE'
:
1
,
'flutter_service_worker.js'
:
1
,
});
expectRequestCounts
(<
String
,
int
>{
''
:
1
,
'index.html'
:
2
,
// We still download some resources multiple times if the server is non-caching.
'main.dart.js'
:
2
,
'assets/FontManifest.json'
:
2
,
'flutter_service_worker.js'
:
1
,
'assets/NOTICES'
:
1
,
'assets/AssetManifest.json'
:
1
,
'CLOSE'
:
1
,
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
if
(!
headless
)
...<
String
,
int
>{
'manifest.json'
:
1
,
'favicon.ico'
:
1
,
}
});
expect
(
reportedVersion
,
'3'
);
reportedVersion
=
null
;
print
(
'No cache: test page reload'
);
await
server
.
chrome
.
reloadPage
();
await
waitForAppToLoad
(<
String
,
int
>{
'CLOSE'
:
1
,
'flutter_service_worker.js'
:
1
,
});
expectRequestCounts
(<
String
,
int
>{
'flutter_service_worker.js'
:
1
,
'CLOSE'
:
1
,
if
(!
headless
)
'manifest.json'
:
1
,
});
expect
(
reportedVersion
,
'3'
);
reportedVersion
=
null
;
print
(
'No cache: test page reload after rebuild'
);
await
_rebuildApp
(
version:
4
);
// TODO(yjbanov): when running Chrome with DevTools protocol, for some
// reason a hard refresh is still required. This works without a hard
// refresh when running Chrome manually as normal. At the time of writing
// this test I wasn't able to figure out what's wrong with the way we run
// Chrome from tests.
await
server
.
chrome
.
reloadPage
(
ignoreCache:
true
);
await
waitForAppToLoad
(<
String
,
int
>{
'CLOSE'
:
1
,
'flutter_service_worker.js'
:
1
,
});
expectRequestCounts
(<
String
,
int
>{
''
:
1
,
'index.html'
:
2
,
'flutter_service_worker.js'
:
2
,
'main.dart.js'
:
2
,
'assets/NOTICES'
:
1
,
'assets/AssetManifest.json'
:
1
,
'assets/FontManifest.json'
:
2
,
'CLOSE'
:
1
,
if
(!
headless
)
...<
String
,
int
>{
'manifest.json'
:
1
,
'favicon.ico'
:
1
,
}
});
expect
(
reportedVersion
,
'4'
);
reportedVersion
=
null
;
}
finally
{
}
finally
{
chrome
?.
stop
();
await
_setAppVersion
(
1
);
await
server
?.
close
();
await
server
?.
stop
();
userDataDirectory
.
deleteSync
(
recursive:
true
);
}
}
// This is a long test. The default 30 seconds is not enough.
},
timeout:
const
Timeout
(
Duration
(
minutes:
10
)));
}
}
dev/bots/test.dart
View file @
e7953b3b
...
@@ -15,6 +15,7 @@ import 'package:path/path.dart' as path;
...
@@ -15,6 +15,7 @@ import 'package:path/path.dart' as path;
import
'browser.dart'
;
import
'browser.dart'
;
import
'flutter_compact_formatter.dart'
;
import
'flutter_compact_formatter.dart'
;
import
'run_command.dart'
;
import
'run_command.dart'
;
import
'service_worker_test.dart'
;
import
'utils.dart'
;
import
'utils.dart'
;
typedef
ShardRunner
=
Future
<
void
>
Function
();
typedef
ShardRunner
=
Future
<
void
>
Function
();
...
@@ -811,6 +812,7 @@ Future<void> _runWebLongRunningTests() async {
...
@@ -811,6 +812,7 @@ Future<void> _runWebLongRunningTests() async {
()
=>
_runGalleryE2eWebTest
(
'profile'
,
canvasKit:
true
),
()
=>
_runGalleryE2eWebTest
(
'profile'
,
canvasKit:
true
),
()
=>
_runGalleryE2eWebTest
(
'release'
),
()
=>
_runGalleryE2eWebTest
(
'release'
),
()
=>
_runGalleryE2eWebTest
(
'release'
,
canvasKit:
true
),
()
=>
_runGalleryE2eWebTest
(
'release'
,
canvasKit:
true
),
()
=>
runWebServiceWorkerTest
(
headless:
true
),
];
];
await
_ensureChromeDriverIsRunning
();
await
_ensureChromeDriverIsRunning
();
await
_runShardRunnerIndexOfTotalSubshard
(
tests
);
await
_runShardRunnerIndexOfTotalSubshard
(
tests
);
...
...
dev/devicelab/lib/framework/browser.dart
View file @
e7953b3b
...
@@ -196,6 +196,10 @@ class Chrome {
...
@@ -196,6 +196,10 @@ class Chrome {
return
data
;
return
data
;
}
}
Future
<
void
>
reloadPage
({
bool
ignoreCache
=
false
})
async
{
await
_debugConnection
.
page
.
reload
(
ignoreCache:
ignoreCache
);
}
/// Stops the Chrome process.
/// Stops the Chrome process.
void
stop
()
{
void
stop
()
{
_isStopped
=
true
;
_isStopped
=
true
;
...
...
dev/integration_tests/flutter_gallery/web/index.html
View file @
e7953b3b
...
@@ -23,12 +23,68 @@ found in the LICENSE file. -->
...
@@ -23,12 +23,68 @@ found in the LICENSE file. -->
application. For more information, see:
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
<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
)
{
if
(
'serviceWorker'
in
navigator
)
{
window
.
addEventListener
(
'flutter-first-frame'
,
function
()
{
// Service workers are supported. Use them.
navigator
.
serviceWorker
.
register
(
'flutter_service_worker.js'
);
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>
</script>
<script
src=
"main.dart.js"
type=
"application/javascript"
></script>
</body>
</body>
</html>
</html>
dev/integration_tests/web/lib/service_worker_test.dart
View file @
e7953b3b
...
@@ -4,16 +4,8 @@
...
@@ -4,16 +4,8 @@
import
'dart:html'
as
html
;
import
'dart:html'
as
html
;
Future
<
void
>
main
()
async
{
Future
<
void
>
main
()
async
{
final
html
.
ServiceWorkerRegistration
worker
=
await
html
.
window
.
navigator
.
serviceWorker
.
ready
;
await
html
.
window
.
navigator
.
serviceWorker
.
ready
;
if
(
worker
.
active
!=
null
)
{
final
String
response
=
'CLOSE?version=1'
;
await
Future
.
delayed
(
const
Duration
(
seconds:
5
));
await
html
.
HttpRequest
.
getString
(
response
);
await
html
.
HttpRequest
.
getString
(
'CLOSE'
);
html
.
document
.
body
.
appendHtml
(
response
);
return
;
}
worker
.
addEventListener
(
'statechange'
,
(
event
)
async
{
if
(
worker
.
active
!=
null
)
{
await
Future
.
delayed
(
const
Duration
(
seconds:
5
));
await
html
.
HttpRequest
.
getString
(
'CLOSE'
);
}
});
}
}
dev/integration_tests/web/web/index.html
View file @
e7953b3b
...
@@ -8,6 +8,10 @@ found in the LICENSE file. -->
...
@@ -8,6 +8,10 @@ found in the LICENSE file. -->
<meta
content=
"IE=Edge"
http-equiv=
"X-UA-Compatible"
>
<meta
content=
"IE=Edge"
http-equiv=
"X-UA-Compatible"
>
<title>
Web Test
</title>
<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"
>
<link
rel=
"manifest"
href=
"manifest.json"
>
</head>
</head>
<body>
<body>
...
@@ -15,12 +19,68 @@ found in the LICENSE file. -->
...
@@ -15,12 +19,68 @@ found in the LICENSE file. -->
application. For more information, see:
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
<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
)
{
if
(
'serviceWorker'
in
navigator
)
{
// Service workers are supported. Use them.
window
.
addEventListener
(
'load'
,
function
()
{
window
.
addEventListener
(
'load'
,
function
()
{
navigator
.
serviceWorker
.
register
(
'flutter_service_worker.js'
);
// 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>
</script>
<script
src=
"main.dart.js"
type=
"application/javascript"
></script>
</body>
</body>
</html>
</html>
packages/flutter_tools/lib/src/build_system/targets/web.dart
View file @
e7953b3b
...
@@ -356,6 +356,12 @@ class WebReleaseBundle extends Target {
...
@@ -356,6 +356,12 @@ class WebReleaseBundle extends Target {
if
(
environment
.
fileSystem
.
path
.
basename
(
inputFile
.
path
)
==
'index.html'
)
{
if
(
environment
.
fileSystem
.
path
.
basename
(
inputFile
.
path
)
==
'index.html'
)
{
final
String
randomHash
=
Random
().
nextInt
(
4294967296
).
toString
();
final
String
randomHash
=
Random
().
nextInt
(
4294967296
).
toString
();
final
String
resultString
=
inputFile
.
readAsStringSync
()
final
String
resultString
=
inputFile
.
readAsStringSync
()
.
replaceFirst
(
'var serviceWorkerVersion = null'
,
"var serviceWorkerVersion = '
$randomHash
'"
,
)
// This is for legacy index.html that still use the old service
// worker loading mechanism.
.
replaceFirst
(
.
replaceFirst
(
"navigator.serviceWorker.register('flutter_service_worker.js')"
,
"navigator.serviceWorker.register('flutter_service_worker.js')"
,
"navigator.serviceWorker.register('flutter_service_worker.js?v=
$randomHash
')"
,
"navigator.serviceWorker.register('flutter_service_worker.js?v=
$randomHash
')"
,
...
@@ -492,7 +498,7 @@ self.addEventListener("install", (event) => {
...
@@ -492,7 +498,7 @@ self.addEventListener("install", (event) => {
return
event
.
waitUntil
(
return
event
.
waitUntil
(
caches
.
open
(
TEMP
).
then
((
cache
)
=>
{
caches
.
open
(
TEMP
).
then
((
cache
)
=>
{
return
cache
.
addAll
(
return
cache
.
addAll
(
CORE
.
map
((
value
)
=>
new
Request
(
value
+
'?revision='
+
RESOURCES
[
value
]
,
{
'cache'
:
'reload'
})));
CORE
.
map
((
value
)
=>
new
Request
(
value
,
{
'cache'
:
'reload'
})));
})
})
);
);
});
});
...
...
packages/flutter_tools/templates/app/web/index.html.tmpl
View file @
e7953b3b
...
@@ -23,9 +23,6 @@
...
@@ -23,9 +23,6 @@
<meta
name=
"apple-mobile-web-app-title"
content=
"{{projectName}}"
>
<meta
name=
"apple-mobile-web-app-title"
content=
"{{projectName}}"
>
<link
rel=
"apple-touch-icon"
href=
"icons/Icon-192.png"
>
<link
rel=
"apple-touch-icon"
href=
"icons/Icon-192.png"
>
<!-- Favicon -->
<link
rel=
"icon"
type=
"image/png"
href=
"favicon.png"
/>
<title>
{{projectName}}
</title>
<title>
{{projectName}}
</title>
<link
rel=
"manifest"
href=
"manifest.json"
>
<link
rel=
"manifest"
href=
"manifest.json"
>
</head>
</head>
...
@@ -34,12 +31,68 @@
...
@@ -34,12 +31,68 @@
application. For more information, see:
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
<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
)
{
if
(
'serviceWorker'
in
navigator
)
{
window
.
addEventListener
(
'flutter-first-frame'
,
function
()
{
// Service workers are supported. Use them.
navigator
.
serviceWorker
.
register
(
'flutter_service_worker.js'
);
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>
</script>
<script
src=
"main.dart.js"
type=
"application/javascript"
></script>
</body>
</body>
</html>
</html>
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