Skip to content

Commit c658c05

Browse files
committed
[webview_flutter_lwe] Fix raster thread crash on WebView disposal
Disposing a WebView unregistered the texture and then immediately destroyed the web engine and its TBM surfaces on the same call. The Flutter raster thread could still be compositing the last frame that referenced a TBM surface, so the surfaces were freed out from under the GPU (tbm_bo_free with a non-zero lock count), crashing the raster thread. Dispose now stops the web engine first, unregisters the texture, and blocks the raster thread from obtaining or touching buffers via a disposing flag. Because the Tizen embedder does not invoke the UnregisterTexture completion callback, the TBM surfaces are released on a later main-loop turn (ownership is moved out of the WebView) so any in-flight composite finishes first. This eliminates the raster-thread crash seen when running the integration test suite, where each test disposes a WebView.
1 parent a190365 commit c658c05

3 files changed

Lines changed: 47 additions & 4 deletions

File tree

packages/webview_flutter_lwe/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Implement `onUrlChange` for the navigation delegate.
44
* Fix navigation delegate callbacks being dropped when the navigation delegate
55
is replaced after the WebView is created.
6+
* Fix a crash on the raster thread when a WebView is disposed while a frame is
7+
still being composited.
68
* Add 1 integration test case.
79

810
## 0.4.1

packages/webview_flutter_lwe/tizen/src/webview.cc

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ WebView::WebView(flutter::PluginRegistrar* registrar, int view_id,
125125

126126
InitWebView();
127127

128-
dispatcher_ = std::make_unique<MessageDispatcher>();
128+
dispatcher_ = std::make_shared<MessageDispatcher>();
129129

130130
webview_channel_ = std::make_unique<FlMethodChannel>(
131131
GetPluginRegistrar()->messenger(), GetWebViewChannelName(),
@@ -261,12 +261,46 @@ std::string WebView::GetNavigationDelegateChannelName() {
261261
}
262262

263263
void WebView::Dispose() {
264-
texture_registrar_->UnregisterTexture(GetTextureId(), nullptr);
265-
264+
// Stop the web engine first so its renderer thread stops writing into the
265+
// shared TBM surfaces before anything is torn down.
266266
if (webview_instance_) {
267267
webview_instance_->Destroy();
268268
webview_instance_ = nullptr;
269269
}
270+
271+
// Flag the view as disposing and detach the buffers under the render lock so
272+
// the raster thread's ObtainGpuSurface() immediately stops handing out (and
273+
// stops touching) buffers that are about to be released.
274+
std::shared_ptr<BufferPool> pool;
275+
{
276+
std::lock_guard<std::mutex> lock(mutex_);
277+
is_disposing_ = true;
278+
working_surface_ = nullptr;
279+
candidate_surface_ = nullptr;
280+
rendered_surface_ = nullptr;
281+
pool = std::move(tbm_pool_);
282+
}
283+
284+
// Unregister the texture with a completion callback. The embedder tears
285+
// down the external texture on the render (raster) thread — after any
286+
// in-flight frame callback for it has finished — and only then invokes this
287+
// callback, so by the time it runs the raster thread is guaranteed done
288+
// with the TBM surfaces. The callback fires on the render thread, so it
289+
// hops to the main thread to actually free the pool. dispatcher_ itself
290+
// (not a raw pointer to it) is captured so that if this callback fires
291+
// after the WebView has been destroyed, the (stateless) dispatcher is kept
292+
// alive by the callback's own shared_ptr copy instead of dangling.
293+
texture_registrar_->UnregisterTexture(
294+
GetTextureId(),
295+
[pool, dispatcher = dispatcher_]() {
296+
// Invoked on the render thread once the texture is fully unregistered.
297+
// Hand the pool to a main-thread task for destruction; by this point
298+
// the raster thread has released all GPU resources and the LWE engine
299+
// has unmapped all TBM surfaces, so no in-flight frames remain.
300+
dispatcher->dispatchTaskOnMainThread([pool]() {
301+
// Pool destructor calls tbm_surface_destroy on all buffers.
302+
});
303+
});
270304
}
271305

272306
void WebView::Resize(double width, double height) {
@@ -806,6 +840,9 @@ void WebView::HandleCookieMethodCall(const FlMethodCall& method_call,
806840
FlutterDesktopGpuSurfaceDescriptor* WebView::ObtainGpuSurface(size_t width,
807841
size_t height) {
808842
std::lock_guard<std::mutex> lock(mutex_);
843+
if (is_disposing_ || !tbm_pool_) {
844+
return nullptr;
845+
}
809846
if (!candidate_surface_) {
810847
if (rendered_surface_) {
811848
return rendered_surface_->GpuSurface();

packages/webview_flutter_lwe/tizen/src/webview.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ class WebView : public PlatformView {
8080
BufferUnit* rendered_surface_ = nullptr;
8181
bool is_mouse_lbutton_down_ = false;
8282
bool has_navigation_delegate_ = false;
83-
std::unique_ptr<MessageDispatcher> dispatcher_;
83+
bool is_disposing_ = false;
84+
// Shared (not unique) so a pending dispose callback can keep the
85+
// dispatcher alive by holding its own reference after the WebView that
86+
// created it has been destroyed.
87+
std::shared_ptr<MessageDispatcher> dispatcher_;
8488
std::unique_ptr<FlMethodChannel> webview_channel_;
8589
std::unique_ptr<FlMethodChannel> navigation_delegate_channel_;
8690
std::unique_ptr<flutter::TextureVariant> texture_variant_;

0 commit comments

Comments
 (0)