diff --git a/lib/ui/channel_buffers.dart b/lib/ui/channel_buffers.dart index a93208b3b8bb7..881d34c39c4af 100644 --- a/lib/ui/channel_buffers.dart +++ b/lib/ui/channel_buffers.dart @@ -380,6 +380,7 @@ class ChannelBuffers { assert(!name.contains('\u0000'), 'Channel names must not contain U+0000 NULL characters.'); final _Channel channel = _channels.putIfAbsent(name, () => _Channel()); channel.setListener(callback); + sendChannelUpdate(name, listening: true); } /// Clears the listener for the specified channel. @@ -392,9 +393,15 @@ class ChannelBuffers { final _Channel? channel = _channels[name]; if (channel != null) { channel.clearListener(); + sendChannelUpdate(name, listening: false); } } + @Native(symbol: 'PlatformConfigurationNativeApi::SendChannelUpdate') + external static void _sendChannelUpdate(String name, bool listening); + + void sendChannelUpdate(String name, {required bool listening}) => _sendChannelUpdate(name, listening); + /// Deprecated. Migrate to [setListener] instead. /// /// Remove and process all stored messages for a given channel. diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index d25754fa96b94..976b373d29d08 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -110,6 +110,7 @@ typedef CanvasPath Path; V(PlatformConfigurationNativeApi::GetRootIsolateToken, 0) \ V(PlatformConfigurationNativeApi::RegisterBackgroundIsolate, 1) \ V(PlatformConfigurationNativeApi::SendPortPlatformMessage, 4) \ + V(PlatformConfigurationNativeApi::SendChannelUpdate, 2) \ V(PlatformConfigurationNativeApi::GetScaledFontSize, 2) \ V(DartRuntimeHooks::Logger_PrintDebugString, 1) \ V(DartRuntimeHooks::Logger_PrintString, 1) \ diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index a5f38a7b03ca1..d19c80a7a8028 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -631,6 +631,12 @@ void PlatformConfigurationNativeApi::RegisterBackgroundIsolate( dart_state->SetPlatformMessageHandler(weak_platform_message_handler); } +void PlatformConfigurationNativeApi::SendChannelUpdate(const std::string& name, + bool listening) { + UIDartState::Current()->platform_configuration()->client()->SendChannelUpdate( + name, listening); +} + double PlatformConfigurationNativeApi::GetScaledFontSize( double unscaled_font_size, int configuration_id) { diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index c8bb6dcef591c..7965ba10a7b91 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -207,6 +207,17 @@ class PlatformConfigurationClient { /// virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id) = 0; + //-------------------------------------------------------------------------- + /// @brief Invoked when a listener is registered on a platform channel. + /// + /// @param[in] name The name of the platform channel to which a + /// listener has been registered or cleared. + /// + /// @param[in] listening Whether the listener has been set (true) or + /// cleared (false). + /// + virtual void SendChannelUpdate(std::string name, bool listening) = 0; + //-------------------------------------------------------------------------- /// @brief Synchronously invokes platform-specific APIs to apply the /// system text scaling on the given unscaled font size. @@ -571,6 +582,8 @@ class PlatformConfigurationNativeApi { static void RespondToPlatformMessage(int response_id, const tonic::DartByteData& data); + static void SendChannelUpdate(const std::string& name, bool listening); + //-------------------------------------------------------------------------- /// @brief Requests the Dart VM to adjusts the GC heuristics based on /// the requested `performance_mode`. Returns the old performance diff --git a/lib/web_ui/lib/channel_buffers.dart b/lib/web_ui/lib/channel_buffers.dart index 4d0e3fb4a4d13..48d777ce2d539 100644 --- a/lib/web_ui/lib/channel_buffers.dart +++ b/lib/web_ui/lib/channel_buffers.dart @@ -257,6 +257,8 @@ class ChannelBuffers { return true; }()); } + + void sendChannelUpdate(String name, {required bool listening}) {} } final ChannelBuffers channelBuffers = ChannelBuffers(); diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 9eb97fa47deb0..9dc6a944de7cc 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -401,6 +401,11 @@ RuntimeController::ComputePlatformResolvedLocale( return client_.ComputePlatformResolvedLocale(supported_locale_data); } +// |PlatformConfigurationClient| +void RuntimeController::SendChannelUpdate(std::string name, bool listening) { + client_.SendChannelUpdate(std::move(name), listening); +} + Dart_Port RuntimeController::GetMainPort() { std::shared_ptr root_isolate = root_isolate_.lock(); return root_isolate ? root_isolate->main_port() : ILLEGAL_PORT; diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 2a36211354d81..7a65f2b074282 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -683,6 +683,9 @@ class RuntimeController : public PlatformConfigurationClient { std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) override; + // |PlatformConfigurationClient| + void SendChannelUpdate(std::string name, bool listening) override; + // |PlatformConfigurationClient| double GetScaledFontSize(double unscaled_font_size, int configuration_id) const override; diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 324ccf0a44e62..a036d4723cb34 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -54,6 +54,8 @@ class RuntimeDelegate { virtual std::weak_ptr GetPlatformMessageHandler() const = 0; + virtual void SendChannelUpdate(std::string name, bool listening) = 0; + virtual double GetScaledFontSize(double unscaled_font_size, int configuration_id) const = 0; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 2228f855e7197..920122c9a23dc 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -570,6 +570,10 @@ std::weak_ptr Engine::GetPlatformMessageHandler() return delegate_.GetPlatformMessageHandler(); } +void Engine::SendChannelUpdate(std::string name, bool listening) { + delegate_.OnEngineChannelUpdate(std::move(name), listening); +} + void Engine::LoadDartDeferredLibrary( intptr_t loading_unit_id, std::unique_ptr snapshot_data, diff --git a/shell/common/engine.h b/shell/common/engine.h index f73d85ffa7e78..f7d888de425f2 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -294,6 +294,17 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { virtual const std::shared_ptr& GetPlatformMessageHandler() const = 0; + //-------------------------------------------------------------------------- + /// @brief Invoked when a listener is registered on a platform channel. + /// + /// @param[in] name The name of the platform channel to which a + /// listener has been registered or cleared. + /// + /// @param[in] listening Whether the listener has been set (true) or + /// cleared (false). + /// + virtual void OnEngineChannelUpdate(std::string name, bool listening) = 0; + //-------------------------------------------------------------------------- /// @brief Synchronously invokes platform-specific APIs to apply the /// system text scaling on the given unscaled font size. @@ -977,6 +988,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { std::weak_ptr GetPlatformMessageHandler() const override; + // |RuntimeDelegate| + void SendChannelUpdate(std::string name, bool listening) override; + // |RuntimeDelegate| double GetScaledFontSize(double unscaled_font_size, int configuration_id) const override; diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 7e6a5e38d0a7e..9d8339321d999 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -39,6 +39,7 @@ class MockDelegate : public Engine::Delegate { MOCK_METHOD0(GetCurrentTimePoint, fml::TimePoint()); MOCK_CONST_METHOD0(GetPlatformMessageHandler, const std::shared_ptr&()); + MOCK_METHOD2(OnEngineChannelUpdate, void(std::string, bool)); MOCK_CONST_METHOD2(GetScaledFontSize, double(double font_size, int configuration_id)); }; @@ -69,6 +70,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { MOCK_METHOD1(RequestDartDeferredLibrary, void(intptr_t)); MOCK_CONST_METHOD0(GetPlatformMessageHandler, std::weak_ptr()); + MOCK_METHOD2(SendChannelUpdate, void(std::string, bool)); MOCK_CONST_METHOD2(GetScaledFontSize, double(double font_size, int configuration_id)); }; diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index 76d93cfc7c87c..48439c252447a 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -114,6 +114,8 @@ void PlatformView::UpdateSemantics( // NOLINTNEXTLINE(performance-unnecessary-value-param) CustomAccessibilityActionUpdates actions) {} +void PlatformView::SendChannelUpdate(const std::string& name, bool listening) {} + void PlatformView::HandlePlatformMessage( std::unique_ptr message) { if (auto response = message->response()) { diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index c5b4f1ea97b64..09ac7ed7c64f7 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -465,6 +465,17 @@ class PlatformView { virtual void UpdateSemantics(SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions); + //---------------------------------------------------------------------------- + /// @brief Used by the framework to tell the embedder that it has + /// registered a listener on a given channel. + /// + /// @param[in] name The name of the channel on which the listener has + /// set or cleared a listener. + /// @param[in] listening True if a listener has been set, false if it has + /// been cleared. + /// + virtual void SendChannelUpdate(const std::string& name, bool listening); + //---------------------------------------------------------------------------- /// @brief Used by embedders to specify the updated viewport metrics for /// a view. In response to this call, on the raster thread, the diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 128607232d7e9..4f71b68594f4c 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1328,6 +1328,18 @@ void Shell::OnEngineHandlePlatformMessage( } } +void Shell::OnEngineChannelUpdate(std::string name, bool listening) { + FML_DCHECK(is_set_up_); + FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + + task_runners_.GetPlatformTaskRunner()->PostTask( + [view = platform_view_->GetWeakPtr(), name = std::move(name), listening] { + if (view) { + view->SendChannelUpdate(name, listening); + } + }); +} + void Shell::HandleEngineSkiaMessage(std::unique_ptr message) { const auto& data = message->data(); diff --git a/shell/common/shell.h b/shell/common/shell.h index e808caec0ef9e..b8c9c31052df6 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -680,6 +680,9 @@ class Shell final : public PlatformView::Delegate, // |Engine::Delegate| fml::TimePoint GetCurrentTimePoint() override; + // |Engine::Delegate| + void OnEngineChannelUpdate(std::string name, bool listening) override; + // |Engine::Delegate| double GetScaledFontSize(double unscaled_font_size, int configuration_id) const override; diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index e571001a26588..b54452c20999d 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -1881,6 +1881,17 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, user_data]() { return ptr(user_data); }; } + flutter::PlatformViewEmbedder::ChanneUpdateCallback channel_update_callback = + nullptr; + if (SAFE_ACCESS(args, channel_update_callback, nullptr) != nullptr) { + channel_update_callback = [ptr = args->channel_update_callback, user_data]( + const std::string& name, bool listening) { + FlutterChannelUpdate update{sizeof(FlutterChannelUpdate), name.c_str(), + listening}; + ptr(&update, user_data); + }; + } + auto external_view_embedder_result = InferExternalViewEmbedderFromArgs( SAFE_ACCESS(args, compositor, nullptr), settings.enable_impeller); if (external_view_embedder_result.second) { @@ -1895,6 +1906,7 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, vsync_callback, // compute_platform_resolved_locale_callback, // on_pre_engine_restart_callback, // + channel_update_callback, // }; auto on_create_platform_view = InferPlatformViewCreationCallback( diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 730a45d18418b..65c959956c17d 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -1402,6 +1402,20 @@ typedef void (*FlutterUpdateSemanticsCallback2)( const FlutterSemanticsUpdate2* /* semantics update */, void* /* user data*/); +/// An update to whether a message channel has a listener set or not. +typedef struct { + // The size of the struct. Must be sizeof(FlutterChannelUpdate). + size_t struct_size; + /// The name of the channel. + const char* channel; + /// True if a listener has been set, false if one has been cleared. + bool listening; +} FlutterChannelUpdate; + +typedef void (*FlutterChannelUpdateCallback)( + const FlutterChannelUpdate* /* channel update */, + void* /* user data */); + typedef struct _FlutterTaskRunner* FlutterTaskRunner; typedef struct { @@ -2194,6 +2208,11 @@ typedef struct { /// and `update_semantics_callback2` may be provided; the others must be set /// to null. FlutterUpdateSemanticsCallback2 update_semantics_callback2; + + /// The callback invoked by the engine in response to a channel listener + /// being registered on the framework side. The callback is invoked from + /// a task posted to the UI task runner. + FlutterChannelUpdateCallback channel_update_callback; } FlutterProjectArgs; #ifndef FLUTTER_ENGINE_NO_PROTOTYPES diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index 5015c1af8ce9d..c48de1daaab81 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -1289,3 +1289,12 @@ void pointer_data_packet() { signalNativeTest(); } + +@pragma('vm:entry-point') +void channel_listener_response() async { + channelBuffers.setListener('test/listen', + (ByteData? data, PlatformMessageResponseCallback callback) { + callback(null); + }); + signalNativeTest(); +} diff --git a/shell/platform/embedder/platform_view_embedder.cc b/shell/platform/embedder/platform_view_embedder.cc index 7e7ed026079f3..01bfbe97cb8eb 100644 --- a/shell/platform/embedder/platform_view_embedder.cc +++ b/shell/platform/embedder/platform_view_embedder.cc @@ -199,6 +199,14 @@ void PlatformViewEmbedder::OnPreEngineRestart() const { } } +// |PlatformView| +void PlatformViewEmbedder::SendChannelUpdate(const std::string& name, + bool listening) { + if (platform_dispatch_table_.on_channel_update != nullptr) { + platform_dispatch_table_.on_channel_update(name, listening); + } +} + std::shared_ptr PlatformViewEmbedder::GetPlatformMessageHandler() const { return platform_message_handler_; diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index 896e11a7101c6..a6452c6b7e18c 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -41,6 +41,7 @@ class PlatformViewEmbedder final : public PlatformView { std::function>( const std::vector& supported_locale_data)>; using OnPreEngineRestartCallback = std::function; + using ChanneUpdateCallback = std::function; struct PlatformDispatchTable { UpdateSemanticsCallback update_semantics_callback; // optional @@ -50,6 +51,7 @@ class PlatformViewEmbedder final : public PlatformView { ComputePlatformResolvedLocaleCallback compute_platform_resolved_locale_callback; OnPreEngineRestartCallback on_pre_engine_restart_callback; // optional + ChanneUpdateCallback on_channel_update; // optional }; // Create a platform view that sets up a software rasterizer. @@ -134,6 +136,9 @@ class PlatformViewEmbedder final : public PlatformView { std::unique_ptr> ComputePlatformResolvedLocales( const std::vector& supported_locale_data) override; + // |PlatformView| + void SendChannelUpdate(const std::string& name, bool listening) override; + FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewEmbedder); }; diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index f12a9e58b130a..48b8ba3686a49 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -114,6 +114,7 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( SetSemanticsCallbackHooks(); SetLogMessageCallbackHook(); SetLocalizationCallbackHooks(); + SetChannelUpdateCallbackHook(); AddCommandLineArgument("--disable-vm-service"); if (preference == InitializationPreference::kSnapshotsInitialize || @@ -263,6 +264,11 @@ void EmbedderConfigBuilder::SetLogMessageCallbackHook() { EmbedderTestContext::GetLogMessageCallbackHook(); } +void EmbedderConfigBuilder::SetChannelUpdateCallbackHook() { + project_args_.channel_update_callback = + context_.GetChannelUpdateCallbackHook(); +} + void EmbedderConfigBuilder::SetLogTag(std::string tag) { log_tag_ = std::move(tag); project_args_.log_tag = log_tag_.c_str(); diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index d3b133c4e402e..dfa9ce4908f08 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -80,6 +80,8 @@ class EmbedderConfigBuilder { // Used to set a custom log message handler. void SetLogMessageCallbackHook(); + void SetChannelUpdateCallbackHook(); + // Used to set a custom log tag. void SetLogTag(std::string tag); diff --git a/shell/platform/embedder/tests/embedder_test_context.cc b/shell/platform/embedder/tests/embedder_test_context.cc index 99b2fe0f5f8af..71dc2309dfe30 100644 --- a/shell/platform/embedder/tests/embedder_test_context.cc +++ b/shell/platform/embedder/tests/embedder_test_context.cc @@ -147,6 +147,11 @@ void EmbedderTestContext::SetPlatformMessageCallback( platform_message_callback_ = callback; } +void EmbedderTestContext::SetChannelUpdateCallback( + const ChannelUpdateCallback& callback) { + channel_update_callback_ = callback; +} + void EmbedderTestContext::PlatformMessageCallback( const FlutterPlatformMessage* message) { if (platform_message_callback_) { @@ -232,6 +237,20 @@ EmbedderTestContext::GetComputePlatformResolvedLocaleCallbackHook() { }; } +FlutterChannelUpdateCallback +EmbedderTestContext::GetChannelUpdateCallbackHook() { + if (channel_update_callback_ == nullptr) { + return nullptr; + } + + return [](const FlutterChannelUpdate* update, void* user_data) { + auto context = reinterpret_cast(user_data); + if (context->channel_update_callback_) { + context->channel_update_callback_(update); + } + }; +} + FlutterTransformation EmbedderTestContext::GetRootSurfaceTransformation() { return FlutterTransformationMake(root_surface_transformation_); } diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index d59afad2d4777..4bb675b2ced06 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -33,6 +33,7 @@ using SemanticsActionCallback = std::function; using LogMessageCallback = std::function; +using ChannelUpdateCallback = std::function; struct AOTDataDeleter { void operator()(FlutterEngineAOTData aot_data) { @@ -89,6 +90,8 @@ class EmbedderTestContext { void SetLogMessageCallback(const LogMessageCallback& log_message_callback); + void SetChannelUpdateCallback(const ChannelUpdateCallback& callback); + std::future> GetNextSceneImage(); EmbedderTestCompositor& GetCompositor(); @@ -132,6 +135,7 @@ class EmbedderTestContext { SemanticsUpdateCallback update_semantics_callback_; SemanticsNodeCallback update_semantics_node_callback_; SemanticsActionCallback update_semantics_custom_action_callback_; + ChannelUpdateCallback channel_update_callback_; std::function platform_message_callback_; LogMessageCallback log_message_callback_; std::unique_ptr compositor_; @@ -155,6 +159,8 @@ class EmbedderTestContext { static FlutterComputePlatformResolvedLocaleCallback GetComputePlatformResolvedLocaleCallbackHook(); + FlutterChannelUpdateCallback GetChannelUpdateCallbackHook(); + void SetupAOTMappingsIfNecessary(); void SetupAOTDataIfNecessary(); diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index cbbf8f5bd9d60..a2f403d8f0ac6 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -2562,6 +2562,37 @@ TEST_F(EmbedderTest, CanSendPointer) { message_latch.Wait(); } +TEST_F(EmbedderTest, RegisterChannelListener) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + + fml::AutoResetWaitableEvent latch; + fml::AutoResetWaitableEvent latch2; + bool listening = false; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments) { latch.Signal(); })); + context.SetChannelUpdateCallback([&](const FlutterChannelUpdate* update) { + EXPECT_STREQ(update->channel, "test/listen"); + EXPECT_TRUE(update->listening); + listening = true; + latch2.Signal(); + }); + + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + builder.SetDartEntrypoint("channel_listener_response"); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + latch.Wait(); + // Drain tasks posted to platform thread task runner. + fml::MessageLoop::GetCurrent().RunExpiredTasksNow(); + latch2.Wait(); + + ASSERT_TRUE(listening); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 6a2169579e6c0..fce1c0531e48c 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -15,6 +15,7 @@ #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" #include "flutter/shell/platform/common/path_utils.h" +#include "flutter/shell/platform/embedder/embedder_struct_macros.h" #include "flutter/shell/platform/windows/accessibility_bridge_windows.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" @@ -373,6 +374,15 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { host->root_isolate_create_callback_(); } }; + args.channel_update_callback = [](const FlutterChannelUpdate* update, + void* user_data) { + auto host = static_cast(user_data); + if (SAFE_ACCESS(update, channel, nullptr) != nullptr) { + std::string channel_name(update->channel); + host->OnChannelUpdate(std::move(channel_name), + SAFE_ACCESS(update, listening, false)); + } + }; args.custom_task_runners = &custom_task_runners; @@ -792,16 +802,6 @@ 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_->BeginProcessingLifecycle(); -} - void FlutterWindowsEngine::OnWindowStateEvent(HWND hwnd, WindowStateEvent event) { lifecycle_manager_->OnWindowStateEvent(hwnd, event); @@ -819,4 +819,12 @@ std::optional FlutterWindowsEngine::ProcessExternalWindowMessage( return std::nullopt; } +void FlutterWindowsEngine::OnChannelUpdate(std::string name, bool listening) { + if (name == "flutter/platform" && listening) { + lifecycle_manager_->BeginProcessingExit(); + } else if (name == "flutter/lifecycle" && listening) { + lifecycle_manager_->BeginProcessingLifecycle(); + } +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 36147c00c821c..cdbcd375fd119 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -258,10 +258,6 @@ class FlutterWindowsEngine { // Called when a WM_DWMCOMPOSITIONCHANGED message is received. void OnDwmCompositionChanged(); - // Called in response to the framework registering a ServiceBindings. - // Registers the top level handler for the WM_CLOSE window message. - void OnApplicationLifecycleEnabled(); - // Called when a Window receives an event that may alter the application // lifecycle state. void OnWindowStateEvent(HWND hwnd, WindowStateEvent event); @@ -301,6 +297,10 @@ class FlutterWindowsEngine { // created. This is typically caused by a hot restart (Shift-R in CLI.) void OnPreEngineRestart(); + // Invoked by the engine when a listener is set or cleared on a platform + // channel. + virtual void OnChannelUpdate(std::string name, bool listening); + private: // Allows swapping out embedder_api_ calls in tests. friend class EngineModifier; diff --git a/shell/platform/windows/flutter_windows_engine_unittests.cc b/shell/platform/windows/flutter_windows_engine_unittests.cc index 66afc098ff17b..f812460989789 100644 --- a/shell/platform/windows/flutter_windows_engine_unittests.cc +++ b/shell/platform/windows/flutter_windows_engine_unittests.cc @@ -703,7 +703,8 @@ TEST_F(FlutterWindowsEngineTest, TestExit) { ON_CALL(*handler, IsLastWindowOfProcess).WillByDefault([]() { return true; }); EXPECT_CALL(*handler, Quit).Times(1); modifier.SetLifecycleManager(std::move(handler)); - engine->OnApplicationLifecycleEnabled(); + + engine->lifecycle_manager()->BeginProcessingExit(); engine->Run(); @@ -740,7 +741,7 @@ TEST_F(FlutterWindowsEngineTest, TestExitCancel) { ON_CALL(*handler, IsLastWindowOfProcess).WillByDefault([]() { return true; }); EXPECT_CALL(*handler, Quit).Times(0); modifier.SetLifecycleManager(std::move(handler)); - engine->OnApplicationLifecycleEnabled(); + engine->lifecycle_manager()->BeginProcessingExit(); auto binary_messenger = std::make_unique(engine->messenger()); @@ -801,7 +802,7 @@ TEST_F(FlutterWindowsEngineTest, TestExitSecondCloseMessage) { hwnd, msg, wparam, lparam); }); modifier.SetLifecycleManager(std::move(handler)); - engine->OnApplicationLifecycleEnabled(); + engine->lifecycle_manager()->BeginProcessingExit(); engine->Run(); @@ -852,7 +853,7 @@ TEST_F(FlutterWindowsEngineTest, TestExitCloseMultiWindow) { // Quit should not be called when there is more than one window. EXPECT_CALL(*handler, Quit).Times(0); modifier.SetLifecycleManager(std::move(handler)); - engine->OnApplicationLifecycleEnabled(); + engine->lifecycle_manager()->BeginProcessingExit(); engine->Run(); @@ -900,7 +901,7 @@ TEST_F(FlutterWindowsEngineTest, EnableApplicationLifecycle) { }); EXPECT_CALL(*handler, IsLastWindowOfProcess).Times(1); modifier.SetLifecycleManager(std::move(handler)); - engine->OnApplicationLifecycleEnabled(); + engine->lifecycle_manager()->BeginProcessingExit(); engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0, 0); @@ -1054,7 +1055,8 @@ TEST_F(FlutterWindowsEngineTest, EnableLifecycleState) { EXPECT_FALSE(finished); // Test that we can set the state afterwards. - engine->OnApplicationLifecycleEnabled(); + + engine->lifecycle_manager()->BeginProcessingLifecycle(); view.OnWindowStateEvent(hwnd, WindowStateEvent::kShow); while (!finished) { @@ -1111,5 +1113,29 @@ TEST_F(FlutterWindowsEngineTest, LifecycleStateToFrom) { } } +TEST_F(FlutterWindowsEngineTest, ChannelListenedTo) { + FlutterWindowsEngineBuilder builder{GetContext()}; + builder.SetDartEntrypoint("enableLifecycleToFrom"); + + 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; }; + + bool lifecycle_began = false; + auto handler = std::make_unique(engine); + handler->begin_processing_callback = [&]() { lifecycle_began = true; }; + modifier.SetLifecycleManager(std::move(handler)); + + engine->Run(); + + while (!lifecycle_began) { + engine->task_runner()->ProcessTasks(); + } +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/platform_handler.cc b/shell/platform/windows/platform_handler.cc index 771d3d56b9b24..c6bdde6b4ca8a 100644 --- a/shell/platform/windows/platform_handler.cc +++ b/shell/platform/windows/platform_handler.cc @@ -498,7 +498,7 @@ void PlatformHandler::HandleMethodCall( SystemSoundPlay(sound_type.GetString(), std::move(result)); } else if (method.compare(kInitializationCompleteMethod) == 0) { - engine_->OnApplicationLifecycleEnabled(); + // Deprecated but should not cause an error. result->Success(); } else { result->NotImplemented(); diff --git a/shell/platform/windows/windows_lifecycle_manager.cc b/shell/platform/windows/windows_lifecycle_manager.cc index fc3bf469cf9e2..a41b1617ba6c2 100644 --- a/shell/platform/windows/windows_lifecycle_manager.cc +++ b/shell/platform/windows/windows_lifecycle_manager.cc @@ -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_lifecycle_) { + if (!process_exit_) { return false; } auto key = std::make_tuple(hwnd, wpar, lpar); @@ -183,6 +183,10 @@ void WindowsLifecycleManager::BeginProcessingLifecycle() { process_lifecycle_ = true; } +void WindowsLifecycleManager::BeginProcessingExit() { + process_exit_ = true; +} + // TODO(schectman): Wait until the platform channel is registered to send // the platform message. // https://github.com/flutter/flutter/issues/131616 diff --git a/shell/platform/windows/windows_lifecycle_manager.h b/shell/platform/windows/windows_lifecycle_manager.h index 18afd82412db6..c56bfc306b0fb 100644 --- a/shell/platform/windows/windows_lifecycle_manager.h +++ b/shell/platform/windows/windows_lifecycle_manager.h @@ -52,10 +52,12 @@ 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 and sending lifecycle state - // update messages. + // Signal to start sending lifecycle state update messages. virtual void BeginProcessingLifecycle(); + // Signal to start consuming WM_CLOSE messages. + virtual void BeginProcessingExit(); + // Update the app lifecycle state in response to a change in window state. // When the app lifecycle state actually changes, this sends a platform // message to the framework notifying it of the state change. @@ -102,6 +104,7 @@ class WindowsLifecycleManager { std::map, int> sent_close_messages_; bool process_lifecycle_ = false; + bool process_exit_ = false; std::set visible_windows_;