GoogleChrome / GoogleChrome/android-browser-helper

Custom Tabs postMessage channel breaks after full-page navigation on Chrome 150 (worked ≤ 149)

Open
#602 4 comments 4 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.