- From: Daniel Murphy <notifications@github.com>
- Date: Thu, 15 Oct 2026 11:59:10 -0700
- To: w3c/manifest <manifest@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <w3c/manifest/issues/1208/6199947738@github.com>
dmurph left a comment (w3c/manifest#1208) I was just chatting about this today, and I realized I never responded here. I know Android uses this pattern heavily (new APIs require using a new API version, which can break old broken or footgun APIs requiring devs to fix that usage), but I had my agent do some more research to see how normal this is, even on the web platform. This pattern—"backward compatibility prevents removing an implicit default globally, so opting into new features requires explicitly specifying the configuration or avoiding the legacy footgun"—is standard across both the Web Platform and mobile/desktop OS SDKs: (or put another way) When a legacy fallback is recognized as a developer footgun, platforms cannot change the default globally without breaking existing apps. Instead, when developers opt into a new capability or API tier, the platform requires them to explicitly declare the safe configuration—even when the legacy fallback would have computed a working value. Agent stuff that I've combed for errors but might still have a few inaccuracies in the areas I'm not super familiar with: > #### 1. Web Platform Precedents (W3C / WHATWG / IETF) > > * **Cookies (`__Host-` prefix rejecting the implicit URL-derived `Path` default — [RFC 6265bis § 4.1.3.2](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-4.1.3.2)):** This is a direct 1:1 structural match to Manifest `id` vs. `start_url`. Under [RFC 6265 § 5.1.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4), if a `Set-Cookie` header omits `Path`, the browser computes a default `Path` from the request URL's directory (which is already `"/"` if set from `https://example.com/`). However, because a URL-derived default path is fragile if the endpoint URL changes, opting into the `__Host-` prefix **forbids relying on the default computed `Path`** and requires an **explicit** `Path=/` attribute—even when the cookie is set from `/` where the default path would already evaluate to `"/"` (see WPT [`cookies/prefix/__host.explicit-path.https.window.js`](https://github.com/web-platform-tests/wpt/blob/master/cookies/prefix/__host.explicit-path.https.window.js)). > * **Cookies (`SameSite=None` and `Partitioned` requiring explicit `Secure` — [RFC 6265bis § 5.4.7](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7)):** Legacy cookies omit `Secure` by default. When a developer opts into cross-site delivery (`SameSite=None`) or partitioned storage (`Partitioned`), the browser rejects the cookie unless `Secure` is also explicitly specified. > * **Fetch / CORS (`credentials: 'include'` and `Authorization` forbidding `*` wildcards — [WHATWG Fetch § 4.9](https://fetch.spec.whatwg.org/#cors-check)):** `Access-Control-Allow-Origin: *` and `Access-Control-Allow-Headers: *` work for standard CORS requests. However, if a developer opts into `credentials: 'include'`, the spec forbids `*` wildcards and requires the server to explicitly echo the exact Origin, Methods, and Headers. Similarly, sending an `Authorization` header (even without credentials) forbids relying on `Access-Control-Allow-Headers: *` and requires `Authorization` to be listed explicitly ([Fetch `#cors-non-wildcard-request-header-name`](https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name)). > * **ES Modules (`<script type="module">` — [WHATWG HTML § 8.1.6.4](https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script) & [ECMA-262 § 11.2.1](https://tc39.es/ecma262/#sec-strict-mode-code)):** Classic `<script>` tags send cross-origin credentials by default, allow `document.write()`, and run in sloppy mode. Opting into `<script type="module">` disables `document.write()`, enforces JS strict mode, and changes the credential mode to `"same-origin"` so developers must explicitly write `crossorigin="use-credentials"` if they want cross-origin credentials sent. (Note: unlike JS strict mode—where the engine can automatically flip a boolean default—the browser cannot invent a URL-independent manifest `id` on the developer's behalf, so requiring an explicit `id` is the only way to unbind identity from `start_url`.) > * **Declarative Shadow DOM (`setHTMLUnsafe` vs. `innerHTML` — [WHATWG HTML § 8.4](https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-sethtmlunsafe)):** To prevent XSS footguns in existing sanitizers, `<template shadowrootmode="open">` is completely ignored by legacy `element.innerHTML` and requires developers to explicitly call the new `element.setHTMLUnsafe()` or `Document.parseHTMLUnsafe()` APIs. > * **Cross-Origin Isolation (`SharedArrayBuffer`, `performance.measureUserAgentSpecificMemory()` — [WHATWG HTML § 7.2.5](https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-cross-origin-isolated-capability)):** Using these capabilities requires the page to opt out of default cross-origin window and subresource behavior by explicitly serving `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (or `credentialless`). > > #### 2. Service Worker Precedents > > * **Service Worker Multi-Scope Discussions ([`w3c/manifest#586 (comment)`](https://github.com/w3c/manifest/issues/586#issuecomment-780781781) & [`w3c/ServiceWorker#1512`](https://github.com/w3c/ServiceWorker/issues/1512)):** > When the Service Worker WG designed multi-scope registrations, `@wanderview` noted the exact same design principle: *"The plan of record in service-worker-land is to default to scope when using the legacy single scope attribute and there is no id. If you are using the new-fangled-scope attribute that supports multiple scope values, etc, then you are required to set an explicit id."* > > #### 3. Mobile & Desktop OS SDK Precedents (Android & Apple WebKit/iOS/macOS) > > * **Android 12 (API 31) Mandatory `android:exported="true|false"` in `AndroidManifest.xml` ([AOSP `ParsedServiceUtils.java` / `ParsedActivityUtils.java`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java)):** > Historically in Android, omitting `android:exported` on an `<activity>`, `<service>`, or `<receiver>` in `AndroidManifest.xml` had an implicit default: `false` if there were no `<intent-filter>`s, and `true` if an `<intent-filter>` was present. That implicit default was a major footgun. Starting in Android 12 (`targetSdkVersion >= 31`, `MISSING_EXPORTED_FLAG`), if a component has an `<intent-filter>` and omits an explicit `android:exported="true"` or `"false"`, **the OS rejects the manifest and fails app installation (`INSTALL_PARSE_FAILED_MANIFEST_MALFORMED`)**—even if the developer wanted `true` (the exact value the legacy default computes). > * **Android 12–14 Explicit Flag Requirements (`PendingIntent`, `BroadcastReceiver`, `foregroundServiceType`):** > * **API 31 (`PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED`):** Creating a `PendingIntent` previously defaulted to mutable if neither flag was passed. Targeting API 31+ throws `IllegalArgumentException` unless the developer explicitly passes `FLAG_IMMUTABLE` or `FLAG_MUTABLE` (even if they want a mutable `PendingIntent`). > * **API 34 (`DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED`):** Calling `Context.registerReceiver()` previously defaulted to exporting the receiver. Targeting API 34+ throws `SecurityException` unless `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` is explicitly passed. > * **API 34 (`FGS_TYPE_NONE_DISABLED_CHANGE_ID`):** Calling `startForeground()` without declaring an explicit `android:foregroundServiceType` in `AndroidManifest.xml` throws `MissingForegroundServiceTypeException`. > * **Apple's WebKit (`linkedOnOrAfterSDKWithBehavior`) & iOS/macOS SDK (`dyld_program_sdk_at_least`):** Inside WebKit itself ([`RuntimeApplicationChecksCocoa.h`](https://github.com/WebKit/WebKit/blob/main/Source/WTF/wtf/cocoa/RuntimeApplicationChecksCocoa.h)), Apple gates dozens of stricter behaviors on `linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior)` so that apps building against a newer iOS/macOS SDK cannot use legacy footguns or implicit fallbacks that remain allowed for older binaries: > * **`[WKWebsiteDataStore init]` (`SDKAlignedBehavior::WKWebsiteDataStoreInitReturningNil`, [`WKWebsiteDataStore.mm`](https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm)):** Apps linked on or after iOS 14 / macOS 11 SDK crash with an `NSGenericException` if they call `[[WKWebsiteDataStore alloc] init]`, forcing developers to explicitly choose `[WKWebsiteDataStore defaultDataStore]` or `[WKWebsiteDataStore nonPersistentDataStore]`. > * **Duplicate completion handler calls (`SDKAlignedBehavior::ExceptionsForDuplicateCompletionHandlerCalls`, [`CompletionHandlerCallChecker.mm`](https://github.com/WebKit/WebKit/blob/main/Source/WebKit/Shared/Cocoa/CompletionHandlerCallChecker.mm)):** Throws `NSInternalInconsistencyException` on newer SDKs instead of silently ignoring duplicate calls. > * **Private KVC access on `WKWebViewConfiguration` (`SDKAlignedBehavior::ThrowOnKVCInstanceVariableAccess`, [`WKWebViewConfiguration.mm`](https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/API/Cocoa/WKWebViewConfiguration.mm)):** Throws `NSUndefinedKeyException` on newer SDKs while logging a warning on older SDKs. > * **`history.pushState` on `file:` URLs (`SDKAlignedBehavior::PushStateFilePathRestriction`, [`History.cpp`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/History.cpp)):** Throws `SecurityError` when modifying `file:` paths on newer SDKs. -- Reply to this email directly or view it on GitHub: https://github.com/w3c/manifest/issues/1208#issuecomment-6199947738 You are receiving this because you are subscribed to this thread. Message ID: <w3c/manifest/issues/1208/6199947738@github.com>
Received on Thursday, 15 October 2026 18:59:15 UTC