Summary
On Android, when no usable System WebView provider is available at launch, wry's
WebView construction throws android.webkit.WebViewFactory$MissingWebViewPackageException
("Failed to load WebView provider: No WebView installed"). Instead of being reported
to the embedder as an error, this exception is silently discarded on the looper
thread and the pending JNI exception is left uncleared, which results in a hard
process abort at the JNI boundary rather than a recoverable Err.
Because WebViewBuilder::build() returns Ok before the WebView is actually
created on Android (construction is queued to the looper thread), there is
currently no way for an app embedding wry on Android to detect or recover from a
missing/disabled/updating WebView provider — the app simply crashes on launch,
with no opportunity to show recovery UI (e.g. prompt the user to enable or update
Android System WebView).
Environment
- wry: 0.55.1 (latest published)
- tao: 0.35.3 (latest published)
- Platform: Android
- The unreleased
dev branch contains the identical Android code path — this
is not fixed there.
Steps to reproduce
- Build an app embedding wry for Android.
- Put the device into a state where no System WebView provider is selectable, e.g.:
- Disable "Android System WebView" (Settings → Apps → Android System WebView → Disable), or
- Launch while the WebView provider is mid-update from the Play Store, or
- Use a device/ROM/image with no WebView provider installed.
- Launch the app.
Expected behavior
The failure to create the WebView should be surfaced to the embedder as a
recoverable error (or via a callback), so the app can present recovery UI instead
of crashing. At minimum, the process should not hard-abort at the JNI boundary —
the pending Java exception should be cleared and the error logged.
Actual behavior
The process aborts (ART terminates the process at the JNI boundary with a pending
exception). The embedder never learns the WebView failed to create, and cannot
present any fallback UI.
Root cause
Verified against the wry-v0.55.1 tag:
-
build() returns Ok before the WebView exists. On Android,
InnerWebView::new() queues creation onto the looper thread and returns
immediately — see src/android/mod.rs:334 (the MainPipe::send(... CreateWebView ...)
path, followed by Ok(Self { .. })). So WebViewBuilder::build() can never
surface the creation failure on Android; the object does not exist yet.
-
The actual construction throws on the looper thread. In
src/android/main_pipe.rs:181, recv() calls new_object(...) to construct the
native RustWebView. RustWebView extends WebView — see
src/android/kotlin/RustWebView.kt:17
(class RustWebView(context: Context, ...) : WebView(context)) — so the
constructor invokes super(context), which loads the WebView provider and
throws MissingWebViewPackageException when none is installed. The resulting
Err(jni::errors::Error::JavaException) is propagated via ? out of recv().
-
The Err is discarded and the pending exception is never cleared. The
looper fd callback registered in wryCreate (src/android/binding.rs) drives
recv() as main_pipe.recv().is_ok() — reducing the Err to a boolean that
merely unregisters the fd callback. There is no ExceptionCheck /
ExceptionClear, no logging, and no path back to the embedder.
-
(Inferred, not spelled out in wry source) The jni crate returns
Error::JavaException when it detects a pending exception and, by contract,
leaves the Java exception pending for the caller to clear. Since wry never
clears it, control returns to the ART runtime with an unhandled pending
exception at the JNI boundary, and ART aborts the process. This matches the
observed hard crash (rather than a catchable Err). This specific step is
inferred from documented jni-crate semantics, not from wry's own source.
For reference, src/android/kotlin/WryActivity.kt already has a version getter
that reads WebView.getCurrentWebViewPackage() (with a PackageManager fallback),
returning "" when no provider is detectable — so the detection primitive is
already reachable from the generated Kotlin, but it is used only for version
reporting, not to gate creation.
Proposed fix
Two complementary changes:
-
Minimal / defensive (stops the abort for everyone). In the Android recv
path (src/android/main_pipe.rs) and/or the wryCreate callback
(src/android/binding.rs), on Err from the RustWebView new_object call,
call env.exception_clear() and log::error!(...) instead of silently
discarding the error. This alone converts a hard process abort into a
controlled failure (no WebView created) that the embedder can survive.
-
Embedder recovery hook (lets apps recover gracefully). Surface the creation
failure to the Rust embedder — e.g. via the existing on_webview_created
mechanism or a new on_webview_error callback/channel — so the app can react
(show native recovery UI, deep-link the user to update Android System WebView,
etc.). Note that making build() itself block and return the error would be a
larger change, since Android construction is architecturally asynchronous on the
looper thread; a callback/channel fits the existing design better.
Related issues
There is currently no existing issue in tauri-apps/wry, tauri-apps/tao, or
tauri-apps/tauri tracking MissingWebViewPackageException (search returns 0
results). The closest related discussions, for cross-linking:
Summary
On Android, when no usable System WebView provider is available at launch, wry's
WebView construction throws
android.webkit.WebViewFactory$MissingWebViewPackageException("Failed to load WebView provider: No WebView installed"). Instead of being reported
to the embedder as an error, this exception is silently discarded on the looper
thread and the pending JNI exception is left uncleared, which results in a hard
process abort at the JNI boundary rather than a recoverable
Err.Because
WebViewBuilder::build()returnsOkbefore the WebView is actuallycreated on Android (construction is queued to the looper thread), there is
currently no way for an app embedding wry on Android to detect or recover from a
missing/disabled/updating WebView provider — the app simply crashes on launch,
with no opportunity to show recovery UI (e.g. prompt the user to enable or update
Android System WebView).
Environment
devbranch contains the identical Android code path — thisis not fixed there.
Steps to reproduce
Expected behavior
The failure to create the WebView should be surfaced to the embedder as a
recoverable error (or via a callback), so the app can present recovery UI instead
of crashing. At minimum, the process should not hard-abort at the JNI boundary —
the pending Java exception should be cleared and the error logged.
Actual behavior
The process aborts (ART terminates the process at the JNI boundary with a pending
exception). The embedder never learns the WebView failed to create, and cannot
present any fallback UI.
Root cause
Verified against the
wry-v0.55.1tag:build()returnsOkbefore the WebView exists. On Android,InnerWebView::new()queues creation onto the looper thread and returnsimmediately — see
src/android/mod.rs:334(theMainPipe::send(... CreateWebView ...)path, followed by
Ok(Self { .. })). SoWebViewBuilder::build()can neversurface the creation failure on Android; the object does not exist yet.
The actual construction throws on the looper thread. In
src/android/main_pipe.rs:181,recv()callsnew_object(...)to construct thenative
RustWebView.RustWebViewextendsWebView— seesrc/android/kotlin/RustWebView.kt:17(
class RustWebView(context: Context, ...) : WebView(context)) — so theconstructor invokes
super(context), which loads the WebView provider andthrows
MissingWebViewPackageExceptionwhen none is installed. The resultingErr(jni::errors::Error::JavaException)is propagated via?out ofrecv().The
Erris discarded and the pending exception is never cleared. Thelooper fd callback registered in
wryCreate(src/android/binding.rs) drivesrecv()asmain_pipe.recv().is_ok()— reducing theErrto a boolean thatmerely unregisters the fd callback. There is no
ExceptionCheck/ExceptionClear, no logging, and no path back to the embedder.(Inferred, not spelled out in wry source) The
jnicrate returnsError::JavaExceptionwhen it detects a pending exception and, by contract,leaves the Java exception pending for the caller to clear. Since wry never
clears it, control returns to the ART runtime with an unhandled pending
exception at the JNI boundary, and ART aborts the process. This matches the
observed hard crash (rather than a catchable
Err). This specific step isinferred from documented
jni-crate semantics, not from wry's own source.For reference,
src/android/kotlin/WryActivity.ktalready has aversiongetterthat reads
WebView.getCurrentWebViewPackage()(with aPackageManagerfallback),returning
""when no provider is detectable — so the detection primitive isalready reachable from the generated Kotlin, but it is used only for version
reporting, not to gate creation.
Proposed fix
Two complementary changes:
Minimal / defensive (stops the abort for everyone). In the Android
recvpath (
src/android/main_pipe.rs) and/or thewryCreatecallback(
src/android/binding.rs), onErrfrom theRustWebViewnew_objectcall,call
env.exception_clear()andlog::error!(...)instead of silentlydiscarding the error. This alone converts a hard process abort into a
controlled failure (no WebView created) that the embedder can survive.
Embedder recovery hook (lets apps recover gracefully). Surface the creation
failure to the Rust embedder — e.g. via the existing
on_webview_createdmechanism or a new
on_webview_errorcallback/channel — so the app can react(show native recovery UI, deep-link the user to update Android System WebView,
etc.). Note that making
build()itself block and return the error would be alarger change, since Android construction is architecturally asynchronous on the
looper thread; a callback/channel fits the existing design better.
Related issues
There is currently no existing issue in
tauri-apps/wry,tauri-apps/tao, ortauri-apps/tauritrackingMissingWebViewPackageException(search returns 0results). The closest related discussions, for cross-linking:
tauri-runtime-wryfails to create webview tauri#11969 — surfacing fatal WebView-creation errors to the appinstead of only logging them (same philosophy on the desktop WebView2 path).
WebView / HarmonyOS); adjacent to provider availability but not the
zero-provider crash.