-
Notifications
You must be signed in to change notification settings - Fork 29.1k
service worker removal #173609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
service worker removal #173609
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request removes the automatic service worker registration from Flutter web applications, replacing it with a cleanup worker designed to remove old caches from previous versions. The changes are well-contained within flutter_tools
, flutter.js
, and the relevant test files. The migration strategy appears sound. I have identified one critical issue in the new test implementation and one medium-severity issue regarding a duplicated test case in the web test suite.
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request removes the automatic service worker registration from Flutter's web loader and replaces the default service worker with a cleanup worker that removes old caches and unregisters itself. The changes are well-implemented and the accompanying test verifies the cleanup behavior. I have a few suggestions to improve test realism and code clarity.
environment: <String, String>{'FLUTTER_WEB': 'true'}, | ||
const String oldCachingWorkerContent = ''' | ||
'use strict'; | ||
const CACHE_NAME = 'flutter-test-cache-v1'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make the test more realistic and thorough, consider using one of the actual old cache names that the cleanup worker is designed to remove, such as flutter-app-cache
. This would not only test the unregistration of the service worker but also implicitly test the cache deletion logic, making the test more robust.
const CACHE_NAME = 'flutter-app-cache';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
* default Flutter service worker resolves immediately. | ||
* @param {import("./types").ServiceWorkerSettings} settings Service worker settings | ||
* @returns {Promise} that resolves when the latest serviceWorker is ready. | ||
* @returns {Promise<void>} that resolves immediately | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment 'default Flutter service worker resolves immediately' is a bit vague. It would be clearer to state that service worker registration is now disabled in flutter.js
and this method is a stub that no longer performs any action, other than warning about the deprecation.
* Disables service worker registration from flutter.js. This is now a no-op,
* and a warning will be logged if any `settings` are provided.
* @param {import("./types").ServiceWorkerSettings} settings Service worker settings
* @returns {Promise<void>} that resolves immediately
*/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed!
var origin = self.location.origin; | ||
var key = event.request.url.substring(origin.length + 1); | ||
// Redirect URLs to the index.html | ||
if (key.indexOf('?v=') != -1) { | ||
key = key.split('?v=')[0]; | ||
} | ||
if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') { | ||
key = '/'; | ||
} | ||
// If the URL is not the RESOURCE list then return to signal that the | ||
// browser should take over. | ||
if (!RESOURCES[key]) { | ||
return; | ||
} | ||
// If the URL is the index.html, perform an online-first request. | ||
if (key == '/') { | ||
return onlineFirst(event); | ||
} | ||
event.respondWith(caches.open(CACHE_NAME) | ||
.then((cache) => { | ||
return cache.match(event.request).then((response) => { | ||
// Either respond with the cached resource, or perform a fetch and | ||
// lazily populate the cache only if the resource was successfully fetched. | ||
return response || fetch(event.request).then((response) => { | ||
if (response && Boolean(response.ok)) { | ||
cache.put(event.request, response.clone()); | ||
} | ||
return response; | ||
|
||
try { | ||
const clients = await self.clients.matchAll({ | ||
type: 'window', | ||
includeUncontrolled: true, | ||
}); | ||
}) | ||
}) | ||
); | ||
}); | ||
self.addEventListener('message', (event) => { | ||
// SkipWaiting can be used to immediately activate a waiting service worker. | ||
// This will also require a page refresh triggered by the main worker. | ||
if (event.data === 'skipWaiting') { | ||
self.skipWaiting(); | ||
return; | ||
} | ||
if (event.data === 'downloadOffline') { | ||
downloadOffline(); | ||
return; | ||
} | ||
}); | ||
// Download offline will check the RESOURCES for all files not in the cache | ||
// and populate them. | ||
async function downloadOffline() { | ||
var resources = []; | ||
var contentCache = await caches.open(CACHE_NAME); | ||
var currentContent = {}; | ||
for (var request of await contentCache.keys()) { | ||
var key = request.url.substring(origin.length + 1); | ||
if (key == "") { | ||
key = "/"; | ||
} | ||
currentContent[key] = true; | ||
} | ||
for (var resourceKey of Object.keys(RESOURCES)) { | ||
if (!currentContent[resourceKey]) { | ||
resources.push(resourceKey); | ||
} | ||
} | ||
return contentCache.addAll(resources); | ||
} | ||
// Attempt to download the resource online before falling back to | ||
// the offline cache. | ||
function onlineFirst(event) { | ||
return event.respondWith( | ||
fetch(event.request).then((response) => { | ||
return caches.open(CACHE_NAME).then((cache) => { | ||
cache.put(event.request, response.clone()); | ||
return response; | ||
}); | ||
}).catch((error) => { | ||
return caches.open(CACHE_NAME).then((cache) => { | ||
return cache.match(event.request).then((response) => { | ||
if (response != null) { | ||
return response; | ||
// Reload clients to ensure they are not using the old service worker. | ||
clients.forEach((client) => { | ||
if (client.url && 'navigate' in client) { | ||
client.navigate(client.url); | ||
} | ||
throw error; | ||
}); | ||
}); | ||
}) | ||
} catch (e) { | ||
console.warn('Failed to navigate service worker clients:', e); | ||
} | ||
})() | ||
); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new service worker implementation is static and does not require any resource map injection. However, the Dart code that generates this service worker in packages/flutter_tools/lib/src/web/file_generators/flutter_service_worker_js.dart
still contains logic to replace $$RESOURCES_MAP
and $$CORE_LIST
placeholders. This logic is now dead code.
While the file containing the generator is not part of this PR, it would be good to simplify generateServiceWorker
function and the call site in packages/flutter_tools/lib/src/build_system/targets/web.dart
to remove the unused parameters and resource map computation as part of this change or in a follow-up PR to improve maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gemini-code-assist can you generate a diff for what you are suggesting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the follow-up! My previous comment highlighted that the new flutter_service_worker.js
is static and no longer uses the $$RESOURCES_MAP
and $$CORE_LIST
placeholders. While this PR correctly updates the flutter_service_worker.js
file itself, the logic that generates this file in packages/flutter_tools/lib/src/web/file_generators/flutter_service_worker_js.dart
and its call site in packages/flutter_tools/lib/src/build_system/targets/web.dart
still contain code to compute and pass these unused parameters.
Since those generator files are not part of this specific pull request, I cannot provide a committable diff directly for them. However, for a follow-up PR, here are the suggested changes to remove that dead code:
1. packages/flutter_tools/lib/src/web/file_generators/flutter_service_worker_js.dart
// Old function signature and body:
// String generateServiceWorker({
// required Map<String, String> resourcesMap,
// required List<String> coreList,
// }) {
// // ... existing logic that uses resourcesMap and coreList to build the old service worker content ...
// }
// New function signature and body:
String generateServiceWorker() {
return '''
'use strict';
const OLD_CACHE_NAMES = ['flutter-app-manifest', 'flutter-app-cache', 'flutter-temp-cache'];
self.addEventListener('install', () => {
self.skipWaiting();
console.log('Deprecated service worker installed. It will not be used.');
});
// remove old caches and unregister the service worker
self.addEventListener('activate', (event) => {
event.waitUntil(
(async () => {
try {
const deletePromises = OLD_CACHE_NAMES.map((key) => self.caches.delete(key));
await Promise.all(deletePromises);
} catch (e) {
console.warn('Failed to delete old service worker caches:', e);
}
try {
await self.registration.unregister();
} catch (e) {
console.warn('Failed to unregister service worker:', e);
}
try {
const clients = await self.clients.matchAll({
type: 'window',
includeUncontrolled: true,
});
// Reload clients to ensure they are not using the old service worker.
clients.forEach((client) => {
if (client.url && 'navigate' in client) {
client.navigate(client.url);
}
});
} catch (e) {
console.warn('Failed to navigate service worker clients:', e);
}
})()
);
});
''';
}
2. packages/flutter_tools/lib/src/build_system/targets/web.dart
// Old call site (inside a build step or function):
// final Map<String, String> resourcesMap = <String, String>{ /* ... compute ... */ };
// final List<String> coreList = <String>[ /* ... compute ... */ ];
// final String serviceWorkerContent = generateServiceWorker(
// resourcesMap: resourcesMap,
// coreList: coreList,
// );
// New call site:
final String serviceWorkerContent = generateServiceWorker();
These changes would remove the now-unused parameters and the logic to compute them, aligning the generator with the new static service worker content.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SydneyBao Gemini is right that we should update that function, but the diff it produced isn't right 😅
We should do something like this:
String generateServiceWorker(
String fileGeneratorsPath, {
required ServiceWorkerStrategy serviceWorkerStrategy,
}) {
if (serviceWorkerStrategy == ServiceWorkerStrategy.none) {
return '';
}
final String flutterServiceWorkerJsPath = globals.localFileSystem.path.join(
fileGeneratorsPath,
'js',
'flutter_service_worker.js',
);
return globals.localFileSystem
.file(flutterServiceWorkerJsPath)
.readAsStringSync();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Fixed it!
dev/bots/service_worker_test.dart
Outdated
} | ||
print('END runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: $headless)'); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
@@ -740,4 +705,4 @@ class WebTestsSuite { | |||
throw Exception('WebDriver not available.'); | |||
} | |||
} | |||
} | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
() => _runWebStackTraceTest('release', 'lib/stack_trace.dart'), | ||
() => _runWebStackTraceTest('release', 'lib/framework_stack_trace.dart'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 are now repeated twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't believe I didn't catch that. Thank you!
() => _runWebStackTraceTest('debug', 'lib/stack_trace.dart'), | ||
() => _runWebStackTraceTest('debug', 'lib/framework_stack_trace.dart'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 didn't exist before. They seem unrelated to the service worker, is there a reason they were added?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must've been added when I copied my code from an older branch. They've been removed. Thank you!
() => runWebCanvasKitUnitTests(), | ||
() => runWebSkwasmUnitTests(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 2 aren't supposed to be run as part of this shard. They are run separately as you can see here:
Lines 134 to 139 in c932faf
// All the unit/widget tests run using `flutter test --platform=chrome` | |
'web_canvaskit_tests': webTestsSuite.runWebCanvasKitUnitTests, | |
// All the unit/widget tests run using `flutter test --platform=chrome --wasm` | |
'web_skwasm_tests': webTestsSuite.runWebSkwasmUnitTests, | |
// All web integration tests | |
'web_long_running_tests': webTestsSuite.webLongRunningTestsRunner, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed!
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
This reverts commit 27e54df. revert
This reverts commit 8aec333.
…r#174235) https://dart.googlesource.com/sdk.git/+log/0d0a0c394381..c153c5259e62 2025-08-21 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-125.0.dev 2025-08-21 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-124.0.dev 2025-08-21 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-123.0.dev 2025-08-21 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-122.0.dev 2025-08-21 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-121.0.dev 2025-08-20 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-120.0.dev 2025-08-20 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-119.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
7ef7927
to
3996aad
Compare
fixed! |
Create a service worker that removes the old cached service worker.
Related issue: 156910
Pre-launch Checklist
///
).