Skip to content

fix(windows): drop Dart replies that outlive their webview - #8

Merged
Y-PLONI merged 1 commit into
Otzaria:masterfrom
palmoni5:fix/windows-js-handler-use-after-free
Aug 21, 2026
Merged

fix(windows): drop Dart replies that outlive their webview#8
Y-PLONI merged 1 commit into
Otzaria:masterfrom
palmoni5:fix/windows-js-handler-use-after-free

Conversation

@palmoni5

Copy link
Copy Markdown
Member

The crash

Closing a plugin tab in Otzaria while an Otzaria.call() round trip was still pending killed the process:

Faulting module: flutter_inappwebview_windows_plugin.dll
Exception code: 0xc0000005 (access violation)

flutter_inappwebview_plugin::UserContentController::createContentWorld +0x56
  at windows/in_app_webview/user_content_controller.cpp:303

Line 303 dereferences webView_ on a UserContentController that the log shows was already destroyed:

[WindowsInAppWebViewWidget] ... calling "dispose" using []
... dealloc CustomPlatformView
... dealloc InAppWebView
... dealloc UserContentController
[sentry] INFO entering signal handler      <-- the pending reply arrives here

Root cause

InAppWebView::onCallJsHandler builds its reply handlers as [this, callHandlerID] and calls evaluateJavascript on this when Dart answers. Nothing keeps the webview alive across that round trip, so a reply that arrives after the webview is gone runs on freed memory. The same pattern is used by every other callback in in_app_webview.cpp (shouldOverrideUrlLoading, shouldInterceptRequest, download and auth callbacks and so on) — the deferral they hold keeps the WebView2 event alive, not the plugin object.

The fix

  • InAppWebView owns a lifetime token (aliveToken_) that expires first thing in its destructor.
  • BaseCallbackResult takes an optional owner weak reference and turns every handler (success, error, notImplemented) into a no-op once that owner is gone, logging the drop in debug builds. Callbacks that set no owner behave exactly as before.
  • All 15 callback sites in in_app_webview.cpp pass the token.

Verification

  • ctest on the example's native test target: 11/11 pass, including 4 new BaseCallbackResult cases covering owner-alive, owner-gone and the no-owner default.
  • Otzaria debug build against this branch, closing a plugin tab repeatedly mid-request: the process stays up and the log shows the reply being dropped right after teardown, where it used to crash.
... dealloc InAppWebView
... dealloc UserContentController
base_callback_result.h(34): dropping a Dart reply that outlived its owner

The test runner now links flutter and flutter_wrapper_plugin, which base_callback_result.h needs.

Every callback handed to the Dart side captures a raw InAppWebView* and
uses it once the reply comes back. Nothing keeps the webview alive in the
meantime, so closing a webview while a call is still in flight - a plugin
tab closed during a slow callHandler round trip, for instance - lands the
reply on freed memory. The crash surfaces inside
UserContentController::createContentWorld, reached from
InAppWebView::evaluateJavascript, right after "dealloc InAppWebView".

Give the webview a lifetime token and hand a weak reference to every
callback it creates. BaseCallbackResult now checks that token before
running any handler, so a late reply is dropped instead of dereferencing
a destroyed object. Callbacks without an owner keep their old behaviour.
@Y-PLONI
Y-PLONI merged commit b58a1d6 into Otzaria:master Aug 21, 2026
1 check passed
@palmoni5
palmoni5 deleted the fix/windows-js-handler-use-after-free branch August 21, 2026 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants