[web] Fix JS crash when FF blocks service workers. (#106072)
This commit is contained in:
parent
2c15e3cae4
commit
b1b1ee9ca6
@ -21,9 +21,11 @@ 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 _targetWithCachedResources = path.join('lib', 'service_worker_test_cached_resources.dart');
|
||||
final String _targetWithBlockedServiceWorkers = path.join('lib', 'service_worker_test_blocked_service_workers.dart');
|
||||
final String _targetPath = path.join(_testAppDirectory, _target);
|
||||
|
||||
enum ServiceWorkerTestType {
|
||||
blockedServiceWorkers,
|
||||
withoutFlutterJs,
|
||||
withFlutterJs,
|
||||
withFlutterJsShort,
|
||||
@ -37,6 +39,7 @@ Future<void> main() async {
|
||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
|
||||
}
|
||||
|
||||
Future<void> _setAppVersion(int version) async {
|
||||
@ -52,6 +55,9 @@ Future<void> _setAppVersion(int version) async {
|
||||
String _testTypeToIndexFile(ServiceWorkerTestType type) {
|
||||
late String indexFile;
|
||||
switch (type) {
|
||||
case ServiceWorkerTestType.blockedServiceWorkers:
|
||||
indexFile = 'index_with_blocked_service_workers.html';
|
||||
break;
|
||||
case ServiceWorkerTestType.withFlutterJs:
|
||||
indexFile = 'index_with_flutterjs.html';
|
||||
break;
|
||||
@ -562,3 +568,89 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
|
||||
|
||||
print('END runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)\n');
|
||||
}
|
||||
|
||||
Future<void> runWebServiceWorkerTestWithBlockedServiceWorkers({
|
||||
required bool headless
|
||||
}) async {
|
||||
final Map<String, int> requestedPathCounts = <String, int>{};
|
||||
void expectRequestCounts(Map<String, int> expectedCounts) =>
|
||||
_expectRequestCounts(expectedCounts, requestedPathCounts);
|
||||
|
||||
AppServer? server;
|
||||
Future<void> waitForAppToLoad(Map<String, int> waitForCounts) async =>
|
||||
_waitForAppToLoad(waitForCounts, requestedPathCounts, server);
|
||||
|
||||
Future<void> startAppServer({
|
||||
required String cacheControl,
|
||||
}) async {
|
||||
final int serverPort = await findAvailablePort();
|
||||
final int browserDebugPort = await findAvailablePort();
|
||||
server = await AppServer.start(
|
||||
headless: headless,
|
||||
cacheControl: cacheControl,
|
||||
// TODO(yjbanov): use a better port disambiguation strategy than trying
|
||||
// to guess what ports other tests use.
|
||||
appUrl: 'http://localhost:$serverPort/index.html',
|
||||
serverPort: serverPort,
|
||||
browserDebugPort: browserDebugPort,
|
||||
appDirectory: _appBuildDirectory,
|
||||
additionalRequestHandlers: <Handler>[
|
||||
(Request request) {
|
||||
final String requestedPath = request.url.path;
|
||||
requestedPathCounts.putIfAbsent(requestedPath, () => 0);
|
||||
requestedPathCounts[requestedPath] = requestedPathCounts[requestedPath]! + 1;
|
||||
if (requestedPath == 'CLOSE') {
|
||||
return Response.ok('OK');
|
||||
}
|
||||
return Response.notFound('');
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 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,
|
||||
);
|
||||
|
||||
print('BEGIN runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)\n');
|
||||
try {
|
||||
await _rebuildApp(version: 1, testType: ServiceWorkerTestType.blockedServiceWorkers, target: _targetWithBlockedServiceWorkers);
|
||||
|
||||
print('Ensure app starts (when service workers are blocked)');
|
||||
await startAppServer(cacheControl: 'max-age=3600');
|
||||
await waitForAppToLoad(<String, int>{
|
||||
'CLOSE': 1,
|
||||
});
|
||||
expectRequestCounts(<String, int>{
|
||||
'index.html': 1,
|
||||
'flutter.js': 1,
|
||||
'main.dart.js': 1,
|
||||
'assets/FontManifest.json': 1,
|
||||
'assets/fonts/MaterialIcons-Regular.otf': 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,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
await runCommand(
|
||||
'mv',
|
||||
<String>[
|
||||
'index_og.html',
|
||||
'index.html',
|
||||
],
|
||||
workingDirectory: _testAppWebDirectory,
|
||||
);
|
||||
await server?.stop();
|
||||
}
|
||||
print('END runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)\n');
|
||||
}
|
||||
|
@ -1087,6 +1087,7 @@ Future<void> _runWebLongRunningTests() async {
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs),
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
|
||||
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
|
||||
() => _runWebStackTraceTest('release', 'lib/stack_trace.dart'),
|
||||
() => _runWebStackTraceTest('profile', 'lib/framework_stack_trace.dart'),
|
||||
|
@ -0,0 +1,10 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:html' as html;
|
||||
Future<void> main() async {
|
||||
const String response = 'CLOSE?version=1';
|
||||
await html.HttpRequest.getString(response);
|
||||
html.document.body?.appendHtml(response);
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<!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>
|
||||
// This is to break the serviceWorker registration, and make it throw a DOMException!
|
||||
// Test for issue https://github.com/flutter/flutter/issues/103972
|
||||
window.navigator.serviceWorker.register = (url) => {
|
||||
console.error('Failed to register/update a ServiceWorker for scope ', url,
|
||||
': Storage access is restricted in this context due to user settings or private browsing mode.');
|
||||
return Promise.reject(new DOMException('The operation is insecure.'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<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>
|
@ -76,6 +76,7 @@ _flutter.loader = null;
|
||||
|
||||
_loadEntrypoint(entrypointUrl) {
|
||||
if (!this._scriptLoaded) {
|
||||
console.debug("Injecting <script> tag.");
|
||||
this._scriptLoaded = new Promise((resolve, reject) => {
|
||||
let scriptTag = document.createElement("script");
|
||||
scriptTag.src = entrypointUrl;
|
||||
@ -96,7 +97,7 @@ _flutter.loader = null;
|
||||
_waitForServiceWorkerActivation(serviceWorker, entrypointUrl) {
|
||||
if (!serviceWorker || serviceWorker.state == "activated") {
|
||||
if (!serviceWorker) {
|
||||
console.warn("Cannot activate a null service worker. Falling back to plain <script> tag.");
|
||||
console.warn("Cannot activate a null service worker.");
|
||||
} else {
|
||||
console.debug("Service worker already active.");
|
||||
}
|
||||
@ -114,7 +115,7 @@ _flutter.loader = null;
|
||||
|
||||
_loadWithServiceWorker(entrypointUrl, serviceWorkerOptions) {
|
||||
if (!("serviceWorker" in navigator) || serviceWorkerOptions == null) {
|
||||
console.warn("Service worker not supported (or configured). Falling back to plain <script> tag.", serviceWorkerOptions);
|
||||
console.warn("Service worker not supported (or configured).", serviceWorkerOptions);
|
||||
return this._loadEntrypoint(entrypointUrl);
|
||||
}
|
||||
|
||||
@ -145,6 +146,11 @@ _flutter.loader = null;
|
||||
console.debug("Loading app from service worker.");
|
||||
return this._loadEntrypoint(entrypointUrl);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// Some exception happened while registering/activating the service worker.
|
||||
console.warn("Failed to register or activate service worker:", error);
|
||||
return this._loadEntrypoint(entrypointUrl);
|
||||
});
|
||||
|
||||
// Timeout race promise
|
||||
@ -153,7 +159,7 @@ _flutter.loader = null;
|
||||
timeout = new Promise((resolve, _) => {
|
||||
setTimeout(() => {
|
||||
if (!this._scriptLoaded) {
|
||||
console.warn("Failed to load app from service worker. Falling back to plain <script> tag.");
|
||||
console.warn("Loading from service worker timed out after", timeoutMillis, "milliseconds.");
|
||||
resolve(this._loadEntrypoint(entrypointUrl));
|
||||
}
|
||||
}, timeoutMillis);
|
||||
|
Loading…
x
Reference in New Issue
Block a user