blob: f8bef5f26c1c1abe9104340c478d76dc18509d32 [file] [view]
# Deep Dive: TWA Launch Parameters Handling
This document provides a technical deep dive into how Trusted Web Activity (TWA)
launch parameters (e.g. file handles passed via Android intents) are securely
routed and delivered to the web application's launch queue in Chromium.
For the design history and rationale, see the
[Design Doc](projects/twa-launch-params/design.md).
## Architecture Overview
TWA launch parameters handling spans across the JNI boundary:
- **Java (`WebAppLaunchHandler`)**: Manages the Android intent lifecycle,
initiates Digital Asset Links (DAL) verification, and triggers C++ actions.
- **C++ (`TwaLaunchQueueTabHelper`)**: Manages the state of active launches,
matches them to C++ navigations, and enqueues parameters to the cross-platform
`LaunchQueue`.
To prevent security vulnerabilities (specifically, delivering file handles to
malicious origins via redirects), the system correlates launch intents with C++
navigations using a unique **launch token** and performs strict scope checks
upon navigation commit.
## Life of a TWA Launch (Sequence Diagram)
The following diagram illustrates the typical flow of a TWA launch where the
navigation commits before DAL verification completes (the most common race
condition scenario):
```mermaid
sequenceDiagram
autonumber
participant Java as WebAppLaunchHandler (Java)
participant Helper as TwaLaunchQueueTabHelper (C++)
participant Nav as NavigationHandle (C++)
participant Queue as LaunchQueue (C++)
Note over Java, Helper: 1. Preparation
Java->>Helper: PrepareForLaunch(token, params)
Note over Helper: Stores in pending_launches_
Note over Java, Nav: 2. Navigation Start
Java->>Nav: Starts Navigation (with token in UI Data)
Helper->>Nav: DidStartNavigation()
Note over Helper: Extracts token, matches pending.<br/>Attaches UserData to Nav.<br/>Adds to active_launches_.
Note over Java: 3. Verification (Async)
Java->>Java: Starts DAL Verification
Note over Nav, Helper: 4. Navigation Commit
Nav->>Helper: DidFinishNavigation(commit)
Note over Helper: Removes from active_launches_
alt Verification already succeeded
Helper->>Queue: Enqueue(params)
else Verification still pending
Helper->>Helper: Move to committed_launches_ (stash)
end
Note over Java, Helper: 5. Verification Complete
Java-->>Helper: OnLaunchVerified(token, success)
alt success && stashed in committed_launches_
Note over Helper: Verifies RFH is active & in-scope
Helper->>Queue: Enqueue(params)
end
```
## Java-side Routing & Client Modes
When a TWA launch intent is received, Java's `WebAppLaunchHandler` determines
how to route the launch parameters based on the requested
`LaunchHandlerClientMode` and whether the current page in the tab is in scope:
- **`NAVIGATE_NEW`**: Java launches a new Android Activity (which creates a new
task).
- **`NAVIGATE_EXISTING`**: Java reuses the existing tab and triggers a C++
navigation with a token (calls `PrepareForLaunch`).
- **`FOCUS_EXISTING`**:
- **In-Scope**: If the current page is already within the TWA's scope, Java
delivers the parameters immediately without navigating (calls
`EnqueueNonNavigating`).
- **Out-of-Scope Fallback**: If the current page is out of scope (e.g. the
user navigated away to an external site), Java falls back to navigating the
existing tab to the target URL (calls `PrepareForLaunch`).
- *Note*: This fallback models the default Android behavior of reusing and
navigating the existing tab when launching an app with a URL (behaving
like `navigate-existing`), rather than opening a new activity.
If the client mode is not specified or set to `AUTO`, Android defaults to
`NAVIGATE_EXISTING` (unlike Desktop which may default to `NAVIGATE_NEW`).
## State Machine
`TwaLaunchQueueTabHelper` tracks launches in three maps:
1. `pending_launches_` (`token -> PendingLaunch`): Holds launches prepared by
Java that have not yet started navigating.
2. `active_launches_` (`token -> NavigationHandle*`): Holds active navigations.
The launch parameters are stored in `TwaLaunchNavigationHandleUserData`
attached to the `NavigationHandle`.
3. `committed_launches_` (`token -> CommittedLaunch`): Holds launches that
committed but are waiting for DAL verification. Stores the parameters and the
target `RenderFrameHost` ID.
## Scope Protections & Edge Cases
The centralized C++ state machine protects against several critical scope and
stability edge cases:
### 1. Cross-Origin Redirects (Race Mitigation)
If a TWA launch starts a navigation to a verified URL, but that page immediately
redirects to a different origin:
- In `DidFinishNavigation`, we check `TwaLaunchQueueDelegate::IsInScope` against
the final committed URL.
- Since the redirected URL is cross-origin/out-of-scope, the check fails.
- The parameters are discarded and NOT enqueued, preventing the redirect target
from accessing the file handles.
### 2. Speculative Loads (Fallback Matching)
If a tab was pre-warmed (e.g. via `CustomTabsSession::mayLaunchUrl`), the
navigation might have started before Java received the launch intent and
prepared the C++ helper. In this case, the navigation will not have a token in
`ChromeNavigationUIData`.
- Java passes `has_speculative_navigation = true` in `PrepareForLaunch`.
- For **initial intents** (cold start), this is true if the tab was created
hidden and the speculated URL matches the target URL.
- For **new intents** (warm start), this is true if a speculative navigation
was started in the existing tab and the speculated URL matches the target
URL.
- In `DidFinishNavigation`, if `user_data` is missing, we perform a fallback
match by comparing the committed URL against `pending_launches_` that are
marked speculative.
- Fallback matching is strictly restricted to speculative launches to prevent
normal/manual navigations from accidentally adopting stale pending launches.
### 3. Aborted Navigations
If a launch navigation is started but then aborted (e.g. replaced by a new user
navigation before commit):
- `DidFinishNavigation` is called with `committed = false`.
- We clean up the `active_launches_` entry to avoid dangling pointers.
- The launch parameters are discarded as the navigation failed. We do not
deliver parameters to a document that did not result from the launch
navigation.
### 4. Stale Launch Cleanup
If a launch commits and is stashed in `committed_launches_` (waiting
verification), but the verification hangs or takes too long, and the user
navigates away:
- When any new primary main-frame navigation commits to a new document, we call
`committed_launches_.clear()`.
- This prevents stale parameters from accumulating in memory and potentially
being delivered if verification completes much later.
### 5. Pre-existing Navigation Commit Race
If a new launch intent arrives while a pre-existing navigation is already in
progress in the tab:
- `PrepareForLaunch` stashes the new parameters in `pending_launches_`.
- If the pre-existing navigation commits before the new launch navigation
starts, we must NOT clear `pending_launches_`.
- To avoid this race, `pending_launches_` is only cleaned up (clearing all
entries, including speculative ones) when a *new* launch is prepared, rather
than on navigation commits.