components/webapps/docs/projects/twa-launch-params/design.md
Trusted Web Activities (TWAs) allow Android applications to display verified web
content in a customized Custom Tab. TWAs support
Web App Launch Handler API, allowing
them to receive launch parameters (including file handles) from the OS (via
share/file intents) and access them via window.launchQueue.setConsumer.
Previously, the launch parameters were stashed in C++ TwaLaunchQueueTabHelper
as a single pending_launch_params_ field. C++ would then attach these
parameters to the next primary main-frame navigation that it observed in
DidStartNavigation.
The previous implementation suffered from a timing race and lack of scope enforcement, leading to a potential race condition (b/523738212):
DidStartNavigation
before the JNI notification arrives, the parameters are left pending. They
would then be blindly attached to the next navigation, which could be an
untrusted page if the TWA immediately redirected.TwaLaunchQueueDelegate::IsInScope was a stub returning true.We centralize the launch state machine in C++ TwaLaunchQueueTabHelper and use
a unique launch_token to correlate Java launch intents with C++ navigations.
int64_t launch_token for each TWA launch.ChromeNavigationUIData::set_twa_launch_token().PrepareForLaunch(token, params, ...).TwaLaunchQueueTabHelper)We track launches in three states:
stateDiagram-v2
[*] --> Pending : PrepareForLaunch(token)
Pending --> Active : DidStartNavigation(token)
Pending --> [*] : OnLaunchVerified(failed)
Active --> Committed_PendingVerify : DidFinishNavigation(commit) && Verification Pending
Active --> Enqueued : DidFinishNavigation(commit) && Verification Success
Active --> [*] : DidFinishNavigation(abort/out-of-scope)
Committed_PendingVerify --> Enqueued : OnLaunchVerified(success)
Committed_PendingVerify --> [*] : OnLaunchVerified(failed) or New Document Commit
pending_launches_ map.DidStartNavigation extracts the
token from ChromeNavigationUIData, finds it in pending_launches_, and
associates the NavigationHandle with the token in active_launches_.
TwaLaunchNavigationHandleUserData is attached to the handle.committed_launches_ along with the
GlobalRenderFrameHostId.LaunchQueue.PrepareForLaunch(webContents, token, startUrl, packageName, fileUris, scopeUrl, hasSpeculativeNavigation):
Prepares the C++ helper for a launch.OnLaunchVerified(webContents, token, success): Called when Java DAL
verification completes. Updates the state:
TwaLaunchNavigationHandleUserData.EnqueueNonNavigating(...): Handles "focus-existing" launches where no
navigation is triggered.mayLaunchUrl), C++ will not find a token in
ChromeNavigationUIData. In DidFinishNavigation, if the navigation
committed and has no TwaLaunchNavigationHandleUserData, C++ falls back to
matching by URL against pending_launches_ that have
has_speculative_navigation set to true.
TabCreationMode.HIDDEN and the target URL matches the speculated URL.mayLaunchUrl
call) by checking if CustomTabActivityTabProvider::getSpeculatedUrl()
matches the target URL.active_launches_ but do not discard the parameters immediately,
allowing them to potentially match a subsequent navigation if it was a
temporary redirect or retry (though normally we expect a new launch intent to
generate a new token).committed_launches_ is
cleared whenever any new primary main frame navigation commits to a
different document.PrepareForLaunch will
stash the new parameters. If the pre-existing navigation commits after
PrepareForLaunch but before the new launch navigation actually starts, we
must ensure we do not discard the newly pending parameters. For this reason,
we do not clear pending_launches_ on arbitrary navigation commits. Instead,
stale entries in pending_launches_ (both speculative and non-speculative)
are cleared when a new launch is prepared.TwaLaunchQueueDelegate::IsInScope is implemented to check if the committed URL
is within the TWA scope passed from Java. This check is performed:
DidFinishNavigation before enqueuing or stashing.OnLaunchVerified before enqueuing.EnqueueNonNavigating before enqueuing.TwaLaunchQueueTabHelper observes navigation destruction
and cleans up its maps to avoid dangling NavigationHandle or
RenderFrameHost pointers.