GoogleChrome / GoogleChrome/android-browser-helper
Custom Tabs postMessage channel breaks after full-page navigation on Chrome 150 (worked ≤ 149)
- Dominant language
- Java
- Stars
- 832
- Forks
- 370
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 6
Description
# Custom Tabs postMessage channel breaks after full-page navigation on Chrome 150 (worked on ≤ 149)
## Environment
| | |
|---|---|
| **androidx.browser** | `` |
| **Works on** | Chrome for Android **137**, and **149.0.7827.201** |
| **Broken on** | Chrome for Android **150.0.7871.x** (``) |
| **Repro** | Consistent across devices; confirmed by downgrading Chrome on the **same** device (150 → 149/137 restores it) |
## Summary
We use the Custom Tabs / TWA **postMessage** bridge (per the [official guide](https://developer.chrome.com/docs/android/post-message-twa) and the `twa-post-message` demo) for two-way native↔web communication.
It worked reliably through **Chrome 149**. Starting with **Chrome 150**, the postMessage channel is no longer usable after a **full-page navigation / reload** in the web content (e.g. logout, which does `window.location` → full document reload). This breaks all native↔web messaging until the app is restarted.
## What works (Chrome ≤ 149, including 149.0.7827.201)
1. `warmup()` → `newSession()` → `requestPostMessageChannel(origin, origin, extras)`.
2. `onRelationshipValidationResult` returns `result = true`.
3. `onMessageChannelReady` fires.
4. `session.postMessage(...)` returns `RESULT_SUCCESS (0)`.
5. Inbound web→native messages arrive in `onPostMessage`.
6. After a **full-page reload** (logout), on `NAVIGATION_FINISHED` we re-request the channel, it re-entangles, and messaging resumes.
## What breaks on Chrome 150
After a full-page reload (fresh document load in the same Custom Tab):
1. `onNavigationEvent(NAVIGATION_FINISHED)` fires as before.
2. `requestPostMessageChannel(...)` returns `true`.
3. `onRelationshipValidationResult` returns `result = true` (origin validates).
4. `onMessageChannelReady` fires.
5. **But `session.postMessage(...)` returns `-3` (`RESULT_FAILURE_MESSAGING_ERROR`)**, and **no inbound web→native message is ever delivered** (`onPostMessage` is never called for the new document).
6. The channel never re-entangles for the reloaded document; it stays dead until the app is restarted.
So on Chrome 150 the API reports success at every step (channel requested → validated → "ready"), yet the actual message port is **not usable** for the freshly-loaded document.
## Digital Asset Links are valid (misconfiguration ruled out)
`use_as_origin` verifies as **linked** for our origin via Google's checker:
```
GET https://digitalassetlinks.googleapis.com/v1/assetlinks:check
?source.web.site=https://
&relation=delegate_permission/common.use_as_origin
&target.android_app.package_name=
&target.android_app.certificate.sha256_fingerprint=
→ { "linked": true, "maxAge": "..." }
```
- `PostMessageService` is declared with `android:exported="true"`.
- Our integration matches the official `twa-post-message` demo.
So this is **not** a manifest / asset-links / integration misconfiguration.
## Additional observation on Chrome 150
Calling `requestPostMessageChannel(...)` appears to **re-trigger `onRelationshipValidationResult` repeatedly**, which did not happen on ≤ 149. If an app re-requests the channel from within `onRelationshipValidationResult`, this now creates a `validate → request → validate` loop that did not exist before — suggesting the validation/entanglement timing around `requestPostMessageChannel` changed in Chrome 150.
## Expected behavior
After a full-page navigation, re-requesting the channel on `NAVIGATION_FINISHED` (with `onMessageChannelReady` firing and validation succeeding) should produce a **usable** channel — `postMessage` should return `RESULT_SUCCESS` and inbound messages should be delivered to `onPostMessage`, as on Chrome ≤ 149.
## Actual behavior
`onMessageChannelReady` fires, but `postMessage` returns `-3` and no inbound messages are delivered; the channel is never usable for the reloaded document.
## Steps to reproduce
1. Integrate postMessage per the official guide (bare `scheme://host` origin, `use_as_origin` in `assetlinks.json`, `PostMessageService` exported).
2. Establish the channel and confirm two-way messaging works.
3. In the web content, trigger a **full-page reload** (e.g. `window.location.href = ...`, as logout does).
4. On `NAVIGATION_FINISHED`, re-request the channel.
5. Observe: `requestPostMessageChannel` → `true`, validation → `true`, `onMessageChannelReady` fires, but `postMessage` → **`-3`** and `onPostMessage` is never called.
6. Downgrade Chrome to 149 / 137 → the **same code works again**.
## Question
Was Custom Tabs postMessage / `use_as_origin` entanglement changed in **Chrome 150** (possibly as part of the June 2026 Custom Tabs origin-validation security hardening)?
If so, **what is the correct way for an app to re-establish a usable postMessage channel after a full-page navigation on Chrome 150+?**
Contributor guide
Research direction
Start with the Custom Tabs postMessage flow around requestPostMessageChannel, onNavigationEvent, onRelationshipValidationResult, onMessageChannelReady, and postMessage, using the official twa-post-message demo as the comparison point. Reproduce the full-page reload on Chrome 150 and compare it with Chrome 149 or 137. Done means identifying the supported re-establishment path or documenting a reproducible upstream Chrome regression with the observed return codes and callbacks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, java
- Domain
- api, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100