Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d87e742

Browse files
authored
[Windows] Fix resize crash (#49935)
#49872 introduced a crash in debug mode if the platform thread starts a window resize in between `OnFrameGenerated` and `OnFramePresented`. Fixes flutter/flutter#141855. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent e0bf027 commit d87e742

File tree

5 files changed

+81
-27
lines changed

5 files changed

+81
-27
lines changed

shell/platform/windows/flutter_windows_view.cc

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -157,38 +157,41 @@ void FlutterWindowsView::ForceRedraw() {
157157
}
158158
}
159159

160-
void FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) {
160+
bool FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) {
161161
// Called on the platform thread.
162162
std::unique_lock<std::mutex> lock(resize_mutex_);
163163

164164
if (!engine_->surface_manager()) {
165165
SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
166-
return;
166+
return true;
167167
}
168168

169+
// We're using OpenGL rendering. Resizing the surface must happen on the
170+
// raster thread.
169171
EGLint surface_width, surface_height;
170172
engine_->surface_manager()->GetSurfaceDimensions(&surface_width,
171173
&surface_height);
172174

173175
bool surface_will_update =
174176
SurfaceWillUpdate(surface_width, surface_height, width, height);
175-
if (surface_will_update) {
176-
resize_status_ = ResizeState::kResizeStarted;
177-
resize_target_width_ = width;
178-
resize_target_height_ = height;
177+
if (!surface_will_update) {
178+
SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
179+
return true;
179180
}
180181

182+
resize_status_ = ResizeState::kResizeStarted;
183+
resize_target_width_ = width;
184+
resize_target_height_ = height;
185+
181186
SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
182187

183-
if (surface_will_update) {
184-
// Block the platform thread until:
185-
// 1. GetFrameBufferId is called with the right frame size.
186-
// 2. Any pending SwapBuffers calls have been invoked.
187-
resize_cv_.wait_for(lock, kWindowResizeTimeout,
188-
[&resize_status = resize_status_] {
189-
return resize_status == ResizeState::kDone;
190-
});
191-
}
188+
// Block the platform thread until a frame is presented with the target
189+
// size. See |OnFrameGenerated|, |OnEmptyFrameGenerated|, and
190+
// |OnFramePresented|.
191+
return resize_cv_.wait_for(lock, kWindowResizeTimeout,
192+
[&resize_status = resize_status_] {
193+
return resize_status == ResizeState::kDone;
194+
});
192195
}
193196

194197
void FlutterWindowsView::OnWindowRepaint() {
@@ -597,10 +600,15 @@ void FlutterWindowsView::OnFramePresented() {
597600
switch (resize_status_) {
598601
case ResizeState::kResizeStarted:
599602
// The caller must first call |OnFrameGenerated| or
600-
// |OnEmptyFrameGenerated| before calling this method. This status
601-
// indicates the caller did not call these methods or ignored their
602-
// result.
603-
FML_UNREACHABLE();
603+
// |OnEmptyFrameGenerated| before calling this method. This
604+
// indicates one of the following:
605+
//
606+
// 1. The caller did not call these methods.
607+
// 2. The caller ignored these methods' result.
608+
// 3. The platform thread started a resize after the caller called these
609+
// methods. We might have presented a frame of the wrong size to the
610+
// view.
611+
return;
604612
case ResizeState::kFrameGenerated: {
605613
// A frame was generated for a pending resize.
606614
// Unblock the platform thread.

shell/platform/windows/flutter_windows_view.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate {
105105
void SetFlutterCursor(HCURSOR cursor);
106106

107107
// |WindowBindingHandlerDelegate|
108-
void OnWindowSizeChanged(size_t width, size_t height) override;
108+
bool OnWindowSizeChanged(size_t width, size_t height) override;
109109

110110
// |WindowBindingHandlerDelegate|
111111
void OnWindowRepaint() override;

shell/platform/windows/flutter_windows_view_unittests.cc

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -845,15 +845,16 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) {
845845
std::thread([&resized_latch, &view]() {
846846
// Start the window resize. This sends the new window metrics
847847
// and then blocks until another thread completes the window resize.
848-
view.OnWindowSizeChanged(500, 500);
848+
EXPECT_TRUE(view.OnWindowSizeChanged(500, 500));
849849
resized_latch.Signal();
850850
}).detach();
851851

852852
// Wait until the platform thread has started the window resize.
853853
metrics_sent_latch.Wait();
854854

855855
// Complete the window resize by reporting a frame with the new window size.
856-
view.OnFrameGenerated(500, 500);
856+
ASSERT_TRUE(view.OnFrameGenerated(500, 500));
857+
view.OnFramePresented();
857858
resized_latch.Wait();
858859
}
859860

@@ -894,7 +895,7 @@ TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) {
894895
std::thread([&resized_latch, &view]() {
895896
// Start the window resize. This sends the new window metrics
896897
// and then blocks until another thread completes the window resize.
897-
view.OnWindowSizeChanged(500, 500);
898+
EXPECT_TRUE(view.OnWindowSizeChanged(500, 500));
898899
resized_latch.Signal();
899900
}).detach();
900901

@@ -903,9 +904,51 @@ TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) {
903904

904905
// Complete the window resize by reporting an empty frame.
905906
view.OnEmptyFrameGenerated();
907+
view.OnFramePresented();
906908
resized_latch.Wait();
907909
}
908910

911+
// A window resize can be interleaved between a frame generation and
912+
// presentation. This should not crash the app. Regression test for:
913+
// https://github.com/flutter/flutter/issues/141855
914+
TEST(FlutterWindowsViewTest, WindowResizeRace) {
915+
std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
916+
EngineModifier modifier(engine.get());
917+
918+
auto window_binding_handler =
919+
std::make_unique<NiceMock<MockWindowBindingHandler>>();
920+
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
921+
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
922+
std::make_unique<MockAngleSurfaceManager>();
923+
924+
EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
925+
926+
FlutterWindowsView view(std::move(window_binding_handler),
927+
std::move(windows_proc_table));
928+
modifier.SetSurfaceManager(std::move(surface_manager));
929+
view.SetEngine(engine.get());
930+
931+
// Begin a frame.
932+
ASSERT_TRUE(view.OnFrameGenerated(100, 100));
933+
934+
// Inject a window resize between the frame generation and
935+
// frame presentation. The new size invalidates the current frame.
936+
fml::AutoResetWaitableEvent resized_latch;
937+
std::thread([&resized_latch, &view]() {
938+
// The resize is never completed. The view times out and returns false.
939+
EXPECT_FALSE(view.OnWindowSizeChanged(500, 500));
940+
resized_latch.Signal();
941+
}).detach();
942+
943+
// Wait until the platform thread has started the window resize.
944+
resized_latch.Wait();
945+
946+
// Complete the invalidated frame while a resize is pending. Although this
947+
// might mean that we presented a frame with the wrong size, this should not
948+
// crash the app.
949+
view.OnFramePresented();
950+
}
951+
909952
TEST(FlutterWindowsViewTest, WindowRepaintTests) {
910953
std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
911954
EngineModifier modifier(engine.get());

shell/platform/windows/testing/mock_window_binding_handler_delegate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate {
1616
public:
1717
MockWindowBindingHandlerDelegate() {}
1818

19-
MOCK_METHOD(void, OnWindowSizeChanged, (size_t, size_t), (override));
19+
MOCK_METHOD(bool, OnWindowSizeChanged, (size_t, size_t), (override));
2020
MOCK_METHOD(void, OnWindowRepaint, (), (override));
2121
MOCK_METHOD(void,
2222
OnPointerMove,

shell/platform/windows/window_binding_handler_delegate.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ class WindowBindingHandlerDelegate {
2020
using KeyEventCallback = std::function<void(bool)>;
2121

2222
// Notifies delegate that backing window size has changed.
23-
// Typically called by currently configured WindowBindingHandler, this is
24-
// called on the platform thread.
25-
virtual void OnWindowSizeChanged(size_t width, size_t height) = 0;
23+
//
24+
// Called by |FlutterWindow| on the platform thread.
25+
//
26+
// Returns true if the delegate completed the window resize synchronously.
27+
// The return value is exposed for unit testing.
28+
virtual bool OnWindowSizeChanged(size_t width, size_t height) = 0;
2629

2730
// Notifies delegate that backing window needs to be repainted.
2831
// Typically called by currently configured WindowBindingHandler.

0 commit comments

Comments
 (0)