components/webapps/docs/android_twa_launch_params.md
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.
TWA launch parameters handling spans across the JNI boundary:
WebAppLaunchHandler): Manages the Android intent lifecycle,
initiates Digital Asset Links (DAL) verification, and triggers C++ actions.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.
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):
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.
Attaches UserData to Nav.
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
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:
EnqueueNonNavigating).PrepareForLaunch).
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).
TwaLaunchQueueTabHelper tracks launches in three maps:
pending_launches_ (token -> PendingLaunch): Holds launches prepared by
Java that have not yet started navigating.active_launches_ (token -> NavigationHandle*): Holds active navigations.
The launch parameters are stored in TwaLaunchNavigationHandleUserData
attached to the NavigationHandle.committed_launches_ (token -> CommittedLaunch): Holds launches that
committed but are waiting for DAL verification. Stores the parameters and the
target RenderFrameHost ID.The centralized C++ state machine protects against several critical scope and stability edge cases:
If a TWA launch starts a navigation to a verified URL, but that page immediately redirects to a different origin:
DidFinishNavigation, we check TwaLaunchQueueDelegate::IsInScope against
the final committed URL.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.
has_speculative_navigation = true in PrepareForLaunch.
DidFinishNavigation, if user_data is missing, we perform a fallback
match by comparing the committed URL against pending_launches_ that are
marked speculative.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.active_launches_ entry to avoid dangling pointers.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:
committed_launches_.clear().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_.pending_launches_.pending_launches_ is only cleaned up (clearing all
entries, including speculative ones) when a new launch is prepared, rather
than on navigation commits.