diff --git a/dev/bots/service_worker_test.dart b/dev/bots/service_worker_test.dart index 019ccdd90b79..b110b6687530 100644 --- a/dev/bots/service_worker_test.dart +++ b/dev/bots/service_worker_test.dart @@ -37,6 +37,8 @@ enum ServiceWorkerTestType { withFlutterJsEntrypointLoadedEvent, // Same as withFlutterJsEntrypointLoadedEvent, but with TrustedTypes enabled. withFlutterJsTrustedTypesOn, + // Uses custom serviceWorkerVersion. + withFlutterJsCustomServiceWorkerVersion, // Entrypoint generated by `flutter create`. generatedEntrypoint, } @@ -58,6 +60,7 @@ Future main() async { await runWebServiceWorkerTestWithCachingResources(headless: false, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn); await runWebServiceWorkerTestWithGeneratedEntrypoint(headless: false); await runWebServiceWorkerTestWithBlockedServiceWorkers(headless: false); + await runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: false); if (hasError) { reportErrorsAndExit('${bold}One or more tests failed.$reset'); @@ -117,6 +120,8 @@ String _testTypeToIndexFile(ServiceWorkerTestType type) { indexFile = 'index_with_flutterjs_entrypoint_loaded.html'; case ServiceWorkerTestType.withFlutterJsTrustedTypesOn: indexFile = 'index_with_flutterjs_el_tt_on.html'; + case ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion: + indexFile = 'index_with_flutterjs_custom_sw_version.html'; case ServiceWorkerTestType.generatedEntrypoint: indexFile = 'generated_entrypoint.html'; } @@ -703,3 +708,137 @@ Future runWebServiceWorkerTestWithBlockedServiceWorkers({ } print('END runWebServiceWorkerTestWithBlockedServiceWorkers(headless: $headless)'); } + +/// Regression test for https://github.com/flutter/flutter/issues/130212. +Future runWebServiceWorkerTestWithCustomServiceWorkerVersion({ + required bool headless, +}) async { + final Map requestedPathCounts = {}; + void expectRequestCounts(Map expectedCounts) => + _expectRequestCounts(expectedCounts, requestedPathCounts); + + AppServer? server; + Future waitForAppToLoad(Map waitForCounts) async => + _waitForAppToLoad(waitForCounts, requestedPathCounts, server); + + Future startAppServer({ + required String cacheControl, + }) async { + final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests(); + final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests(); + 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: [ + (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', + [ + 'index.html', + 'index_og.html', + ], + workingDirectory: _testAppWebDirectory, + ); + + print('BEGIN runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: $headless)'); + try { + await _rebuildApp(version: 1, testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion, target: _target); + + print('Test page load'); + await startAppServer(cacheControl: 'max-age=0'); + await waitForAppToLoad({ + 'CLOSE': 1, + 'flutter_service_worker.js': 1, + 'assets/fonts/MaterialIcons-Regular.otf': 1, + }); + expectRequestCounts({ + 'index.html': 2, + 'flutter.js': 1, + 'main.dart.js': 1, + 'CLOSE': 1, + 'flutter_service_worker.js': 1, + 'assets/FontManifest.json': 1, + 'assets/AssetManifest.json': 1, + 'assets/fonts/MaterialIcons-Regular.otf': 1, + // In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'. + if (!headless) + ...{ + 'manifest.json': 1, + 'favicon.ico': 1, + }, + }); + + print('Test page reload, ensure service worker is not reloaded'); + await server!.chrome.reloadPage(ignoreCache: true); + await waitForAppToLoad({ + 'CLOSE': 1, + 'flutter.js': 1, + }); + expectRequestCounts({ + '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) + ...{ + 'manifest.json': 1, + 'favicon.ico': 1, + }, + }); + + print('Test page reload after rebuild, ensure service worker is not reloaded'); + await _rebuildApp(version: 1, testType: ServiceWorkerTestType.withFlutterJsCustomServiceWorkerVersion, target: _target); + await server!.chrome.reloadPage(ignoreCache: true); + await waitForAppToLoad({ + 'CLOSE': 1, + 'flutter.js': 1, + }); + expectRequestCounts({ + '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) + ...{ + 'manifest.json': 1, + 'favicon.ico': 1, + }, + }); + } finally { + await runCommand( + 'mv', + [ + 'index_og.html', + 'index.html', + ], + workingDirectory: _testAppWebDirectory, + ); + await server?.stop(); + } + print('END runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: $headless)'); +} diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 8d9dd00c6ce3..30618d6279d4 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -1249,6 +1249,7 @@ Future _runWebLongRunningTests() async { () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn), () => runWebServiceWorkerTestWithGeneratedEntrypoint(headless: true), () => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true), + () => runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: true), () => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'), () => _runWebStackTraceTest('release', 'lib/stack_trace.dart'), () => _runWebStackTraceTest('profile', 'lib/framework_stack_trace.dart'), diff --git a/dev/integration_tests/web/web/index_with_flutterjs_custom_sw_version.html b/dev/integration_tests/web/web/index_with_flutterjs_custom_sw_version.html new file mode 100644 index 000000000000..886afa4df72c --- /dev/null +++ b/dev/integration_tests/web/web/index_with_flutterjs_custom_sw_version.html @@ -0,0 +1,39 @@ + + + + + + + + Integration test. App load with flutter.js. Custom serviceWorkerVersion provided. + + + + + + + + + + + + + + diff --git a/packages/flutter_tools/lib/src/web/file_generators/js/flutter.js b/packages/flutter_tools/lib/src/web/file_generators/js/flutter.js index c60bf3555e5a..4fd0e51f345e 100644 --- a/packages/flutter_tools/lib/src/web/file_generators/js/flutter.js +++ b/packages/flutter_tools/lib/src/web/file_generators/js/flutter.js @@ -144,7 +144,7 @@ _flutter.loader = null; const serviceWorkerActivation = navigator.serviceWorker .register(url) - .then(this._getNewServiceWorker) + .then((serviceWorkerRegistration) => this._getNewServiceWorker(serviceWorkerRegistration, serviceWorkerVersion)) .then(this._waitForServiceWorkerActivation); // Timeout race promise @@ -162,9 +162,10 @@ _flutter.loader = null; * awaiting to be installed/updated. * * @param {ServiceWorkerRegistration} serviceWorkerRegistration + * @param {String} serviceWorkerVersion * @returns {Promise} */ - async _getNewServiceWorker(serviceWorkerRegistration) { + async _getNewServiceWorker(serviceWorkerRegistration, serviceWorkerVersion) { if (!serviceWorkerRegistration.active && (serviceWorkerRegistration.installing || serviceWorkerRegistration.waiting)) { // No active web worker and we have installed or are installing // one for the first time. Simply wait for it to activate.