fix(windows): drop Dart replies that outlive their webview - #8
Merged
Y-PLONI merged 1 commit intoAug 21, 2026
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The crash
Closing a plugin tab in Otzaria while an
Otzaria.call()round trip was still pending killed the process:Line 303 dereferences
webView_on aUserContentControllerthat the log shows was already destroyed:Root cause
InAppWebView::onCallJsHandlerbuilds its reply handlers as[this, callHandlerID]and callsevaluateJavascriptonthiswhen 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 inin_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
InAppWebViewowns a lifetime token (aliveToken_) that expires first thing in its destructor.BaseCallbackResulttakes an optionalownerweak 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.in_app_webview.cpppass the token.Verification
cteston the example's native test target: 11/11 pass, including 4 newBaseCallbackResultcases covering owner-alive, owner-gone and the no-owner default.The test runner now links
flutterandflutter_wrapper_plugin, whichbase_callback_result.hneeds.