From c2bfb97ed1cab1ca3ebf93f6578a5173a6cb5474 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 2 Jul 2019 16:42:59 -0700 Subject: [PATCH] Allow embedders to add callbacks for responses to platform messages from the framework. Fixes https://github.com/flutter/flutter/issues/18852 --- ci/licenses_golden/licenses_flutter | 2 + shell/platform/embedder/BUILD.gn | 3 + shell/platform/embedder/embedder.cc | 52 ++++++++++- shell/platform/embedder/embedder.h | 40 +++++++- shell/platform/embedder/embedder_engine.cc | 7 +- shell/platform/embedder/embedder_engine.h | 3 + .../embedder_platform_message_response.cc | 37 ++++++++ .../embedder_platform_message_response.h | 53 +++++++++++ shell/platform/embedder/fixtures/main.dart | 9 ++ .../platform/embedder/tests/embedder_test.cc | 3 +- shell/platform/embedder/tests/embedder_test.h | 3 +- .../embedder/tests/embedder_unittests.cc | 91 +++++++++++++++++++ 12 files changed, 294 insertions(+), 9 deletions(-) create mode 100644 shell/platform/embedder/embedder_platform_message_response.cc create mode 100644 shell/platform/embedder/embedder_platform_message_response.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e26337a93ee93..6d79b2a77d723 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -768,6 +768,8 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_engine.h FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.cc FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.h FILE: ../../../flutter/shell/platform/embedder/embedder_include.c +FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.h FILE: ../../../flutter/shell/platform/embedder/embedder_safe_access.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface.h diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index a1cca30f46ea7..1173a4f778715 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -23,6 +23,8 @@ source_set("embedder") { "embedder_external_texture_gl.cc", "embedder_external_texture_gl.h", "embedder_include.c", + "embedder_platform_message_response.cc", + "embedder_platform_message_response.h", "embedder_safe_access.h", "embedder_surface.cc", "embedder_surface.h", @@ -46,6 +48,7 @@ source_set("embedder") { "$flutter_root/common", "$flutter_root/flow", "$flutter_root/fml", + "$flutter_root/lib/ui", "$flutter_root/runtime:libdart", "$flutter_root/shell/common", "//third_party/dart/runtime/bin:dart_io_api", diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 2bcd84fce5600..11c05c86dad0d 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -34,6 +34,7 @@ extern const intptr_t kPlatformStrongDillSize; #include "flutter/shell/common/switches.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder_engine.h" +#include "flutter/shell/platform/embedder/embedder_platform_message_response.h" #include "flutter/shell/platform/embedder/embedder_safe_access.h" #include "flutter/shell/platform/embedder/embedder_task_runner.h" #include "flutter/shell/platform/embedder/embedder_thread_host.h" @@ -818,12 +819,20 @@ FlutterEngineResult FlutterEngineSendPlatformMessage( return LOG_EMBEDDER_ERROR(kInvalidArguments); } + const FlutterPlatformMessageResponseHandle* response_handle = + SAFE_ACCESS(flutter_message, response_handle, nullptr); + + fml::RefPtr response; + if (response_handle->message) { + response = response_handle->message->response(); + } + auto message = fml::MakeRefCounted( flutter_message->channel, std::vector( flutter_message->message, flutter_message->message + flutter_message->message_size), - nullptr); + response); return reinterpret_cast(engine) ->SendPlatformMessage(std::move(message)) @@ -831,6 +840,47 @@ FlutterEngineResult FlutterEngineSendPlatformMessage( : LOG_EMBEDDER_ERROR(kInvalidArguments); } +FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( + FlutterEngine engine, + FlutterDataCallback data_callback, + void* user_data, + FlutterPlatformMessageResponseHandle** response_out) { + if (engine == nullptr || data_callback == nullptr || + response_out == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + + flutter::EmbedderPlatformMessageResponse::Callback response_callback = + [user_data, data_callback](const uint8_t* data, size_t size) { + data_callback(data, size, user_data); + }; + + auto platform_task_runner = reinterpret_cast(engine) + ->GetTaskRunners() + .GetPlatformTaskRunner(); + + auto handle = new FlutterPlatformMessageResponseHandle(); + + handle->message = fml::MakeRefCounted( + "", // The channel is empty and unused as the response handle is going to + // referenced directly in the |FlutterEngineSendPlatformMessage| with + // the container message discarded. + fml::MakeRefCounted( + std::move(platform_task_runner), response_callback)); + *response_out = handle; + return kSuccess; +} + +FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle( + FlutterEngine engine, + FlutterPlatformMessageResponseHandle* response) { + if (engine == nullptr || response == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + delete response; + return kSuccess; +} + FlutterEngineResult FlutterEngineSendPlatformMessageResponse( FlutterEngine engine, const FlutterPlatformMessageResponseHandle* handle, diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index e6afed179b5bc..2d6e4205e35d5 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -373,12 +373,11 @@ typedef struct { size_t struct_size; const char* channel; const uint8_t* message; - const size_t message_size; + size_t message_size; // The response handle on which to invoke - // |FlutterEngineSendPlatformMessageResponse| when the response is ready. This - // field is ignored for messages being sent from the embedder to the - // framework. |FlutterEngineSendPlatformMessageResponse| must be called for - // all messages received by the embedder. Failure to call + // |FlutterEngineSendPlatformMessageResponse| when the response is ready. + // |FlutterEngineSendPlatformMessageResponse| must be called for all messages + // received by the embedder. Failure to call // |FlutterEngineSendPlatformMessageResponse| will cause a memory leak. It is // not safe to send multiple responses on a single response object. const FlutterPlatformMessageResponseHandle* response_handle; @@ -388,6 +387,10 @@ typedef void (*FlutterPlatformMessageCallback)( const FlutterPlatformMessage* /* message*/, void* /* user data */); +typedef void (*FlutterDataCallback)(const uint8_t* /* data */, + size_t /* size */, + void* /* user data */); + typedef struct { double left; double top; @@ -707,6 +710,33 @@ FlutterEngineResult FlutterEngineSendPlatformMessage( FlutterEngine engine, const FlutterPlatformMessage* message); +// Creates a platform message response handle that allows the embedder to set a +// native callback for a response to a message. This handle may be set on the +// |response_handle| field of any |FlutterPlatformMessage| sent to the engine. +// +// The handle must be collected via a call to +// |FlutterPlatformMessageReleaseResponseHandle|. This may be done immediately +// after a call to |FlutterEngineSendPlatformMessage| with a platform message +// whose response handle contains the handle created using this call. In case a +// handle is created but never sent in a message, the release call must still be +// made. Not calling release on the handle results in a small memory leak. +// +// The user data baton passed to the data callback is the one specified in this +// call as the third argument. +FLUTTER_EXPORT +FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( + FlutterEngine engine, + FlutterDataCallback data_callback, + void* user_data, + FlutterPlatformMessageResponseHandle** response_out); + +// Collects the handle created using +// |FlutterPlatformMessageCreateResponseHandle|. +FLUTTER_EXPORT +FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle( + FlutterEngine engine, + FlutterPlatformMessageResponseHandle* response); + FLUTTER_EXPORT FlutterEngineResult FlutterEngineSendPlatformMessageResponse( FlutterEngine engine, diff --git a/shell/platform/embedder/embedder_engine.cc b/shell/platform/embedder/embedder_engine.cc index 79c8cf2f8b672..1dd109bfd53eb 100644 --- a/shell/platform/embedder/embedder_engine.cc +++ b/shell/platform/embedder/embedder_engine.cc @@ -18,7 +18,8 @@ EmbedderEngine::EmbedderEngine( EmbedderExternalTextureGL::ExternalTextureCallback external_texture_callback) : thread_host_(std::move(thread_host)), - shell_(Shell::Create(std::move(task_runners), + task_runners_(task_runners), + shell_(Shell::Create(task_runners_, std::move(settings), on_create_platform_view, on_create_rasterizer)), @@ -36,6 +37,10 @@ bool EmbedderEngine::IsValid() const { return is_valid_; } +const TaskRunners& EmbedderEngine::GetTaskRunners() const { + return task_runners_; +} + bool EmbedderEngine::NotifyCreated() { if (!IsValid()) { return false; diff --git a/shell/platform/embedder/embedder_engine.h b/shell/platform/embedder/embedder_engine.h index ab962066ea0d9..dfae111ba6e81 100644 --- a/shell/platform/embedder/embedder_engine.h +++ b/shell/platform/embedder/embedder_engine.h @@ -32,6 +32,8 @@ class EmbedderEngine { ~EmbedderEngine(); + const TaskRunners& GetTaskRunners() const; + bool NotifyCreated(); bool NotifyDestroyed(); @@ -71,6 +73,7 @@ class EmbedderEngine { private: const std::unique_ptr thread_host_; + TaskRunners task_runners_; std::unique_ptr shell_; const EmbedderExternalTextureGL::ExternalTextureCallback external_texture_callback_; diff --git a/shell/platform/embedder/embedder_platform_message_response.cc b/shell/platform/embedder/embedder_platform_message_response.cc new file mode 100644 index 0000000000000..11c03d1f7431b --- /dev/null +++ b/shell/platform/embedder/embedder_platform_message_response.cc @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/embedder/embedder_platform_message_response.h" + +#include "flutter/fml/make_copyable.h" + +namespace flutter { + +EmbedderPlatformMessageResponse::EmbedderPlatformMessageResponse( + fml::RefPtr runner, + Callback callback) + : runner_(std::move(runner)), callback_(callback) {} + +EmbedderPlatformMessageResponse::~EmbedderPlatformMessageResponse() = default; + +// |PlatformMessageResponse| +void EmbedderPlatformMessageResponse::Complete( + std::unique_ptr data) { + if (!data) { + CompleteEmpty(); + return; + } + + runner_->PostTask( + fml::MakeCopyable([data = std::move(data), callback = callback_]() { + callback(data->GetMapping(), data->GetSize()); + })); +} + +// |PlatformMessageResponse| +void EmbedderPlatformMessageResponse::CompleteEmpty() { + Complete(std::make_unique(nullptr, 0u)); +} + +} // namespace flutter diff --git a/shell/platform/embedder/embedder_platform_message_response.h b/shell/platform/embedder/embedder_platform_message_response.h new file mode 100644 index 0000000000000..c93dfc21653f3 --- /dev/null +++ b/shell/platform/embedder/embedder_platform_message_response.h @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_PLATFORM_MESSAGE_RESPONSE_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_PLATFORM_MESSAGE_RESPONSE_H_ + +#include "flutter/fml/macros.h" +#include "flutter/fml/task_runner.h" +#include "flutter/lib/ui/window/platform_message_response.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// @brief The platform message response subclass for responses to messages +/// from the embedder to the framework. Message responses are +/// fulfilled by the framework. +class EmbedderPlatformMessageResponse : public PlatformMessageResponse { + public: + using Callback = std::function; + + //---------------------------------------------------------------------------- + /// @param[in] runner The task runner on which to execute the callback. + /// The response will be initiated by the framework on + /// the UI thread. + /// @param[in] callback The callback that communicates to the embedder the + /// contents of the response sent by the framework back + /// to the emebder. + EmbedderPlatformMessageResponse(fml::RefPtr runner, + Callback callback); + + //---------------------------------------------------------------------------- + /// @brief Destroys the message response. Can be called on any thread. + /// Does not execute unfulfilled callbacks. + /// + ~EmbedderPlatformMessageResponse() override; + + private: + fml::RefPtr runner_; + Callback callback_; + + // |PlatformMessageResponse| + void Complete(std::unique_ptr data) override; + + // |PlatformMessageResponse| + void CompleteEmpty() override; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderPlatformMessageResponse); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_PLATFORM_MESSAGE_RESPONSE_H_ diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index 7c7d320770ded..3ec683f17d83d 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -144,3 +144,12 @@ void a11y_main() async { // ignore: non_constant_identifier_names await semanticsChanged; notifySemanticsEnabled(window.semanticsEnabled); } + + +@pragma('vm:entry-point') +void platform_messages_response() { + window.onPlatformMessage = (String name, ByteData data, PlatformMessageResponseCallback callback) { + callback(data); + }; + signalNativeTest(); +} diff --git a/shell/platform/embedder/tests/embedder_test.cc b/shell/platform/embedder/tests/embedder_test.cc index 89ca660ba762c..62192fd253899 100644 --- a/shell/platform/embedder/tests/embedder_test.cc +++ b/shell/platform/embedder/tests/embedder_test.cc @@ -27,12 +27,13 @@ EmbedderContext& EmbedderTest::GetEmbedderContext() { // |testing::Test| void EmbedderTest::SetUp() { - // Nothing to do here since we will lazily setup the context when asked. + ThreadTest::SetUp(); } // |testing::Test| void EmbedderTest::TearDown() { embedder_context_.reset(); + ThreadTest::TearDown(); } } // namespace testing diff --git a/shell/platform/embedder/tests/embedder_test.h b/shell/platform/embedder/tests/embedder_test.h index 1112c8339b75b..ab1717400cf15 100644 --- a/shell/platform/embedder/tests/embedder_test.h +++ b/shell/platform/embedder/tests/embedder_test.h @@ -10,11 +10,12 @@ #include "flutter/fml/macros.h" #include "flutter/shell/platform/embedder/tests/embedder_context.h" #include "flutter/testing/testing.h" +#include "flutter/testing/thread_test.h" namespace flutter { namespace testing { -class EmbedderTest : public ::testing::Test { +class EmbedderTest : public ThreadTest { public: EmbedderTest(); diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 58c75b3292ead..c5f1551052a77 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -261,5 +261,96 @@ TEST_F(EmbedderTest, IsolateServiceIdSent) { kill_latch.Wait(); } +//------------------------------------------------------------------------------ +/// Creates a platform message response callbacks, does NOT send them, and +/// immediately collects the same. +/// +TEST_F(EmbedderTest, CanCreateAndCollectCallbacks) { + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetDartEntrypoint("platform_messages_response"); + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY([](Dart_NativeArguments args) {})); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + FlutterPlatformMessageResponseHandle* response_handle = nullptr; + auto callback = [](const uint8_t* data, size_t size, + void* user_data) -> void {}; + auto result = FlutterPlatformMessageCreateResponseHandle( + engine.get(), callback, nullptr, &response_handle); + ASSERT_EQ(result, kSuccess); + ASSERT_NE(response_handle, nullptr); + + result = FlutterPlatformMessageReleaseResponseHandle(engine.get(), + response_handle); + ASSERT_EQ(result, kSuccess); +} + +//------------------------------------------------------------------------------ +/// Sends platform messages to Dart code than simply echoes the contents of the +/// message back to the embedder. The embedder registers a native callback to +/// intercept that message. +/// +TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { + struct Captures { + fml::AutoResetWaitableEvent latch; + std::thread::id thread_id; + }; + Captures captures; + + GetThreadTaskRunner()->PostTask([&]() { + captures.thread_id = std::this_thread::get_id(); + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetDartEntrypoint("platform_messages_response"); + + fml::AutoResetWaitableEvent ready; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY( + [&ready](Dart_NativeArguments args) { ready.Signal(); })); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + static std::string kMessageData = "Hello from embedder."; + + FlutterPlatformMessageResponseHandle* response_handle = nullptr; + auto callback = [](const uint8_t* data, size_t size, + void* user_data) -> void { + ASSERT_EQ(size, kMessageData.size()); + ASSERT_EQ(strncmp(reinterpret_cast(kMessageData.data()), + reinterpret_cast(data), size), + 0); + auto captures = reinterpret_cast(user_data); + ASSERT_EQ(captures->thread_id, std::this_thread::get_id()); + captures->latch.Signal(); + }; + auto result = FlutterPlatformMessageCreateResponseHandle( + engine.get(), callback, &captures, &response_handle); + ASSERT_EQ(result, kSuccess); + + FlutterPlatformMessage message = {}; + message.struct_size = sizeof(FlutterPlatformMessage); + message.channel = "test_channel"; + message.message = reinterpret_cast(kMessageData.data()); + message.message_size = kMessageData.size(); + message.response_handle = response_handle; + + ready.Wait(); + result = FlutterEngineSendPlatformMessage(engine.get(), &message); + ASSERT_EQ(result, kSuccess); + + result = FlutterPlatformMessageReleaseResponseHandle(engine.get(), + response_handle); + ASSERT_EQ(result, kSuccess); + }); + + captures.latch.Wait(); +} + } // namespace testing } // namespace flutter