diff --git a/shell/platform/windows/fixtures/main.dart b/shell/platform/windows/fixtures/main.dart index b6f8934536a05..3e1c25eb6da0e 100644 --- a/shell/platform/windows/fixtures/main.dart +++ b/shell/platform/windows/fixtures/main.dart @@ -127,6 +127,38 @@ void exitTestCancel() async { await exited.future; } +@pragma('vm:entry-point') +void enableLifecycleTest() async { + final Completer finished = Completer(); + ui.channelBuffers.setListener('flutter/lifecycle', (ByteData? data, ui.PlatformMessageResponseCallback callback) async { + if (data != null) { + ui.PlatformDispatcher.instance.sendPlatformMessage( + 'flutter/unittest', + data, + (ByteData? reply) { + finished.complete(); + }); + } + }); + await finished.future; +} + +@pragma('vm:entry-point') +void enableLifecycleToFrom() async { + ui.channelBuffers.setListener('flutter/lifecycle', (ByteData? data, ui.PlatformMessageResponseCallback callback) async { + if (data != null) { + ui.PlatformDispatcher.instance.sendPlatformMessage( + 'flutter/unittest', + data, + (ByteData? reply) {}); + } + }); + final Completer enabledLifecycle = Completer(); + ui.PlatformDispatcher.instance.sendPlatformMessage('flutter/platform', ByteData.sublistView(utf8.encode('{"method":"System.initializationComplete"}')), (ByteData? data) { + enabledLifecycle.complete(data); + }); +} + @pragma('vm:entry-point') void customEntrypoint() {} diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 7dff62eff82d0..6a2169579e6c0 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -792,8 +792,14 @@ void FlutterWindowsEngine::OnDwmCompositionChanged() { view_->OnDwmCompositionChanged(); } +// TODO(yaakovschectman): This enables the flutter/lifecycle channel +// once the System.initializationComplete message is received on +// the flutter/system channel. This is a short-term workaround to +// ensure the framework is initialized and ready to accept lifecycle +// messages. This cross-channel dependency should be removed. +// See: https://github.com/flutter/flutter/issues/132514 void FlutterWindowsEngine::OnApplicationLifecycleEnabled() { - lifecycle_manager_->BeginProcessingClose(); + lifecycle_manager_->BeginProcessingLifecycle(); } void FlutterWindowsEngine::OnWindowStateEvent(HWND hwnd, diff --git a/shell/platform/windows/flutter_windows_engine_unittests.cc b/shell/platform/windows/flutter_windows_engine_unittests.cc index 8dcb50846f23c..66afc098ff17b 100644 --- a/shell/platform/windows/flutter_windows_engine_unittests.cc +++ b/shell/platform/windows/flutter_windows_engine_unittests.cc @@ -670,6 +670,15 @@ class MockWindowsLifecycleManager : public WindowsLifecycleManager { MOCK_METHOD4(DispatchMessage, void(HWND, UINT, WPARAM, LPARAM)); MOCK_METHOD0(IsLastWindowOfProcess, bool(void)); MOCK_METHOD1(SetLifecycleState, void(AppLifecycleState)); + + void BeginProcessingLifecycle() override { + WindowsLifecycleManager::BeginProcessingLifecycle(); + if (begin_processing_callback) { + begin_processing_callback(); + } + } + + std::function begin_processing_callback = nullptr; }; TEST_F(FlutterWindowsEngineTest, TestExit) { @@ -1002,5 +1011,105 @@ TEST_F(FlutterWindowsEngineTest, InnerWindowHidden) { AppLifecycleState::kInactive); } +TEST_F(FlutterWindowsEngineTest, EnableLifecycleState) { + FlutterWindowsEngineBuilder builder{GetContext()}; + builder.SetDartEntrypoint("enableLifecycleTest"); + bool finished = false; + + auto window_binding_handler = + std::make_unique<::testing::NiceMock>(); + MockFlutterWindowsView view(std::move(window_binding_handler)); + view.SetEngine(builder.Build()); + FlutterWindowsEngine* engine = view.GetEngine(); + + EngineModifier modifier(engine); + modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; }; + auto handler = std::make_unique(engine); + ON_CALL(*handler, SetLifecycleState) + .WillByDefault([handler_ptr = handler.get()](AppLifecycleState state) { + handler_ptr->WindowsLifecycleManager::SetLifecycleState(state); + }); + modifier.SetLifecycleManager(std::move(handler)); + + auto binary_messenger = + std::make_unique(engine->messenger()); + // Mark the test only as completed on receiving an inactive state message. + binary_messenger->SetMessageHandler( + "flutter/unittest", [&finished](const uint8_t* message, + size_t message_size, BinaryReply reply) { + std::string contents(message, message + message_size); + EXPECT_NE(contents.find("AppLifecycleState.inactive"), + std::string::npos); + finished = true; + char response[] = ""; + reply(reinterpret_cast(response), 0); + }); + + engine->Run(); + + // Test that setting the state before enabling lifecycle does nothing. + HWND hwnd = reinterpret_cast(1); + view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow); + view.OnWindowStateEvent(hwnd, WindowStateEvent::kHide); + EXPECT_FALSE(finished); + + // Test that we can set the state afterwards. + engine->OnApplicationLifecycleEnabled(); + view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow); + + while (!finished) { + engine->task_runner()->ProcessTasks(); + } +} + +TEST_F(FlutterWindowsEngineTest, LifecycleStateToFrom) { + FlutterWindowsEngineBuilder builder{GetContext()}; + builder.SetDartEntrypoint("enableLifecycleToFrom"); + bool enabled_lifecycle = false; + bool dart_responded = false; + + auto window_binding_handler = + std::make_unique<::testing::NiceMock>(); + MockFlutterWindowsView view(std::move(window_binding_handler)); + view.SetEngine(builder.Build()); + FlutterWindowsEngine* engine = view.GetEngine(); + + EngineModifier modifier(engine); + modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; }; + auto handler = std::make_unique(engine); + ON_CALL(*handler, SetLifecycleState) + .WillByDefault([handler_ptr = handler.get()](AppLifecycleState state) { + handler_ptr->WindowsLifecycleManager::SetLifecycleState(state); + }); + handler->begin_processing_callback = [&]() { enabled_lifecycle = true; }; + modifier.SetLifecycleManager(std::move(handler)); + + auto binary_messenger = + std::make_unique(engine->messenger()); + binary_messenger->SetMessageHandler( + "flutter/unittest", + [&](const uint8_t* message, size_t message_size, BinaryReply reply) { + std::string contents(message, message + message_size); + EXPECT_NE(contents.find("AppLifecycleState."), std::string::npos); + dart_responded = true; + char response[] = ""; + reply(reinterpret_cast(response), 0); + }); + + engine->Run(); + + while (!enabled_lifecycle) { + engine->task_runner()->ProcessTasks(); + } + + HWND hwnd = reinterpret_cast(1); + view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow); + view.OnWindowStateEvent(hwnd, WindowStateEvent::kHide); + + while (!dart_responded) { + engine->task_runner()->ProcessTasks(); + } +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/windows_lifecycle_manager.cc b/shell/platform/windows/windows_lifecycle_manager.cc index 5d572e165924e..fc3bf469cf9e2 100644 --- a/shell/platform/windows/windows_lifecycle_manager.cc +++ b/shell/platform/windows/windows_lifecycle_manager.cc @@ -14,7 +14,7 @@ namespace flutter { WindowsLifecycleManager::WindowsLifecycleManager(FlutterWindowsEngine* engine) - : engine_(engine), process_close_(false) {} + : engine_(engine) {} WindowsLifecycleManager::~WindowsLifecycleManager() {} @@ -49,7 +49,7 @@ bool WindowsLifecycleManager::WindowProc(HWND hwnd, // is, we re-dispatch a new WM_CLOSE message. In order to allow the new // message to reach other delegates, we ignore it here. case WM_CLOSE: { - if (!process_close_) { + if (!process_lifecycle_) { return false; } auto key = std::make_tuple(hwnd, wpar, lpar); @@ -179,8 +179,8 @@ bool WindowsLifecycleManager::IsLastWindowOfProcess() { return num_windows <= 1; } -void WindowsLifecycleManager::BeginProcessingClose() { - process_close_ = true; +void WindowsLifecycleManager::BeginProcessingLifecycle() { + process_lifecycle_ = true; } // TODO(schectman): Wait until the platform channel is registered to send @@ -191,7 +191,7 @@ void WindowsLifecycleManager::SetLifecycleState(AppLifecycleState state) { return; } state_ = state; - if (engine_) { + if (engine_ && process_lifecycle_) { const char* state_name = AppLifecycleStateToString(state); engine_->SendPlatformMessage("flutter/lifecycle", reinterpret_cast(state_name), diff --git a/shell/platform/windows/windows_lifecycle_manager.h b/shell/platform/windows/windows_lifecycle_manager.h index 2a2ca7a3160c1..18afd82412db6 100644 --- a/shell/platform/windows/windows_lifecycle_manager.h +++ b/shell/platform/windows/windows_lifecycle_manager.h @@ -52,8 +52,9 @@ class WindowsLifecycleManager { // update the application lifecycle. bool WindowProc(HWND hwnd, UINT msg, WPARAM w, LPARAM l, LRESULT* result); - // Signal to start consuming WM_CLOSE messages. - void BeginProcessingClose(); + // Signal to start consuming WM_CLOSE messages and sending lifecycle state + // update messages. + virtual void BeginProcessingLifecycle(); // Update the app lifecycle state in response to a change in window state. // When the app lifecycle state actually changes, this sends a platform @@ -100,7 +101,7 @@ class WindowsLifecycleManager { std::map, int> sent_close_messages_; - bool process_close_; + bool process_lifecycle_ = false; std::set visible_windows_;