[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 _appBuildDirectory = path.join(_testAppDirectory, 'build', 'web');
|
||||||
final String _target = path.join('lib', 'service_worker_test.dart');
|
final String _target = path.join('lib', 'service_worker_test.dart');
|
||||||
final String _targetWithCachedResources = path.join('lib', 'service_worker_test_cached_resources.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);
|
final String _targetPath = path.join(_testAppDirectory, _target);
|
||||||
|
|
||||||
enum ServiceWorkerTestType {
|
enum ServiceWorkerTestType {
|
||||||
|
blockedServiceWorkers,
|
||||||
withoutFlutterJs,
|
withoutFlutterJs,
|
||||||
withFlutterJs,
|
withFlutterJs,
|
||||||
withFlutterJsShort,
|
withFlutterJsShort,
|
||||||
@ -37,6 +39,7 @@ Future<void> main() async {
|
|||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withoutFlutterJs);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJs);
|
||||||
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsShort);
|
||||||
|
await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setAppVersion(int version) async {
|
Future<void> _setAppVersion(int version) async {
|
||||||
@ -52,6 +55,9 @@ Future<void> _setAppVersion(int version) async {
|
|||||||
String _testTypeToIndexFile(ServiceWorkerTestType type) {
|
String _testTypeToIndexFile(ServiceWorkerTestType type) {
|
||||||
late String indexFile;
|
late String indexFile;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case ServiceWorkerTestType.blockedServiceWorkers:
|
||||||
|
indexFile = 'index_with_blocked_service_workers.html';
|
||||||
|
break;
|
||||||
case ServiceWorkerTestType.withFlutterJs:
|
case ServiceWorkerTestType.withFlutterJs:
|
||||||
indexFile = 'index_with_flutterjs.html';
|
indexFile = 'index_with_flutterjs.html';
|
||||||
break;
|
break;
|
||||||
@ -562,3 +568,89 @@ Future<void> runWebServiceWorkerTestWithCachingResources({
|
|||||||
|
|
||||||
print('END runWebServiceWorkerTestWithCachingResources(headless: $headless, testType: $testType)\n');
|
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.withoutFlutterJs),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs),
|
||||||
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
() => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort),
|
||||||
|
() => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true),
|
||||||
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
|
() => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'),
|
||||||
() => _runWebStackTraceTest('release', 'lib/stack_trace.dart'),
|
() => _runWebStackTraceTest('release', 'lib/stack_trace.dart'),
|
||||||
() => _runWebStackTraceTest('profile', 'lib/framework_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) {
|
_loadEntrypoint(entrypointUrl) {
|
||||||
if (!this._scriptLoaded) {
|
if (!this._scriptLoaded) {
|
||||||
|
console.debug("Injecting <script> tag.");
|
||||||
this._scriptLoaded = new Promise((resolve, reject) => {
|
this._scriptLoaded = new Promise((resolve, reject) => {
|
||||||
let scriptTag = document.createElement("script");
|
let scriptTag = document.createElement("script");
|
||||||
scriptTag.src = entrypointUrl;
|
scriptTag.src = entrypointUrl;
|
||||||
@ -96,7 +97,7 @@ _flutter.loader = null;
|
|||||||
_waitForServiceWorkerActivation(serviceWorker, entrypointUrl) {
|
_waitForServiceWorkerActivation(serviceWorker, entrypointUrl) {
|
||||||
if (!serviceWorker || serviceWorker.state == "activated") {
|
if (!serviceWorker || serviceWorker.state == "activated") {
|
||||||
if (!serviceWorker) {
|
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 {
|
} else {
|
||||||
console.debug("Service worker already active.");
|
console.debug("Service worker already active.");
|
||||||
}
|
}
|
||||||
@ -114,7 +115,7 @@ _flutter.loader = null;
|
|||||||
|
|
||||||
_loadWithServiceWorker(entrypointUrl, serviceWorkerOptions) {
|
_loadWithServiceWorker(entrypointUrl, serviceWorkerOptions) {
|
||||||
if (!("serviceWorker" in navigator) || serviceWorkerOptions == null) {
|
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);
|
return this._loadEntrypoint(entrypointUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +146,11 @@ _flutter.loader = null;
|
|||||||
console.debug("Loading app from service worker.");
|
console.debug("Loading app from service worker.");
|
||||||
return this._loadEntrypoint(entrypointUrl);
|
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
|
// Timeout race promise
|
||||||
@ -153,7 +159,7 @@ _flutter.loader = null;
|
|||||||
timeout = new Promise((resolve, _) => {
|
timeout = new Promise((resolve, _) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this._scriptLoaded) {
|
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));
|
resolve(this._loadEntrypoint(entrypointUrl));
|
||||||
}
|
}
|
||||||
}, timeoutMillis);
|
}, timeoutMillis);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user