[Windows] Add/remove view failures should not hang (#52164)
If the embedder API's `FlutterEngineAddView` and `FlutterEngineRemoveView` don't return `kSuccess`, their callbacks won't be invoked. See: [flutter.dev/go/multi-view-embedder-apis](https://flutter.dev/go/multi-view-embedder-apis).
Previously, the Windows embedder would hang in this scenario as it blocks until the callbacks are invoked. Now, the Windows embedder only blocks if these embedder APIs return `kSuccess`.
Kudos to @dkwingsmt for catching this!
Part of https://github.com/flutter/flutter/issues/144810
Part of https://github.com/flutter/flutter/issues/142845
[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc
index bd17054..6962136 100644
--- a/shell/platform/windows/flutter_windows_engine.cc
+++ b/shell/platform/windows/flutter_windows_engine.cc
@@ -532,7 +532,15 @@
captures->latch.Signal();
};
- embedder_api_.AddView(engine_, &info);
+ FlutterEngineResult result = embedder_api_.AddView(engine_, &info);
+ if (result != kSuccess) {
+ FML_LOG(ERROR)
+ << "Starting the add view operation failed. FlutterEngineAddView "
+ "returned an unexpected result: "
+ << result << ". This indicates a bug in the Windows embedder.";
+ FML_DCHECK(false);
+ return nullptr;
+ }
// Block the platform thread until the engine has added the view.
// TODO(loicsharma): This blocks the platform thread eagerly and can
@@ -573,15 +581,24 @@
info.view_id = view_id;
info.user_data = &captures;
info.remove_view_callback = [](const FlutterRemoveViewResult* result) {
- // This is invoked on the raster thread, the same thread that the present
- // callback is invoked. If |FlutterRemoveViewResult.removed| is `true`,
- // the engine guarantees the view won't be presented.
+ // This is invoked on an engine thread. If
+ // |FlutterRemoveViewResult.removed| is `true`, the engine guarantees the
+ // view won't be presented.
Captures* captures = reinterpret_cast<Captures*>(result->user_data);
captures->removed = result->removed;
captures->latch.Signal();
};
- embedder_api_.RemoveView(engine_, &info);
+ FlutterEngineResult result = embedder_api_.RemoveView(engine_, &info);
+ if (result != kSuccess) {
+ FML_LOG(ERROR) << "Starting the remove view operation failed. "
+ "FlutterEngineRemoveView "
+ "returned an unexpected result: "
+ << result
+ << ". This indicates a bug in the Windows embedder.";
+ FML_DCHECK(false);
+ return;
+ }
// Block the platform thread until the engine has removed the view.
// TODO(loicsharma): This blocks the platform thread eagerly and can
diff --git a/shell/platform/windows/flutter_windows_engine_unittests.cc b/shell/platform/windows/flutter_windows_engine_unittests.cc
index f9ebdbc..a7b45f7 100644
--- a/shell/platform/windows/flutter_windows_engine_unittests.cc
+++ b/shell/platform/windows/flutter_windows_engine_unittests.cc
@@ -1265,5 +1265,53 @@
}
}
+TEST_F(FlutterWindowsEngineTest, AddViewFailureDoesNotHang) {
+ FlutterWindowsEngineBuilder builder{GetContext()};
+ auto engine = builder.Build();
+
+ EngineModifier modifier{engine.get()};
+
+ modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
+ modifier.embedder_api().AddView = MOCK_ENGINE_PROC(
+ AddView,
+ [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
+ const FlutterAddViewInfo* info) { return kInternalInconsistency; });
+
+ ASSERT_TRUE(engine->Run());
+
+ // Create the first view. This is the implicit view and isn't added to the
+ // engine.
+ auto implicit_window = std::make_unique<NiceMock<MockWindowBindingHandler>>();
+
+ std::unique_ptr<FlutterWindowsView> implicit_view =
+ engine->CreateView(std::move(implicit_window));
+
+ EXPECT_TRUE(implicit_view);
+
+ // Create a second view. The embedder attempts to add it to the engine.
+ auto second_window = std::make_unique<NiceMock<MockWindowBindingHandler>>();
+
+ EXPECT_DEBUG_DEATH(engine->CreateView(std::move(second_window)),
+ "FlutterEngineAddView returned an unexpected result");
+}
+
+TEST_F(FlutterWindowsEngineTest, RemoveViewFailureDoesNotHang) {
+ FlutterWindowsEngineBuilder builder{GetContext()};
+ builder.SetDartEntrypoint("sendCreatePlatformViewMethod");
+ auto engine = builder.Build();
+
+ EngineModifier modifier{engine.get()};
+
+ modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
+ modifier.embedder_api().RemoveView = MOCK_ENGINE_PROC(
+ RemoveView,
+ [](FLUTTER_API_SYMBOL(FlutterEngine) engine,
+ const FlutterRemoveViewInfo* info) { return kInternalInconsistency; });
+
+ ASSERT_TRUE(engine->Run());
+ EXPECT_DEBUG_DEATH(engine->RemoveView(123),
+ "FlutterEngineRemoveView returned an unexpected result");
+}
+
} // namespace testing
} // namespace flutter