Skip to content

Android: WebView creation failure (MissingWebViewPackageException) aborts the process instead of surfacing an error #1785

Description

@Will5

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

  1. Build an app embedding wry for Android.
  2. 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.
  3. 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:

  1. 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.

  2. 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().

  3. 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.

  4. (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:

  1. 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.

  2. 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:

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions