[Embedder] Send key data through message channel (flutter/engine#29795)
This PR changes how embedder API's SendKeyData sends ui.KeyData to the framework. The packets are now sent over the existing platform messenger, reusing the entirety of its code path and functionalities while keeping the embedder API unchanged
This commit is contained in:
@@ -88,11 +88,6 @@ void _dispatchPointerDataPacket(ByteData packet) {
|
||||
PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _dispatchKeyData(ByteData packet, int responseId) {
|
||||
PlatformDispatcher.instance._dispatchKeyData(packet, responseId);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _dispatchSemanticsAction(int id, int action, ByteData? args) {
|
||||
PlatformDispatcher.instance._dispatchSemanticsAction(id, action, args);
|
||||
|
||||
@@ -28,9 +28,6 @@ typedef TimingsCallback = void Function(List<FrameTiming> timings);
|
||||
/// Signature for [PlatformDispatcher.onPointerDataPacket].
|
||||
typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
|
||||
|
||||
// Signature for the response to KeyDataCallback.
|
||||
typedef _KeyDataResponseCallback = void Function(int responseId, bool handled);
|
||||
|
||||
/// Signature for [PlatformDispatcher.onKeyData].
|
||||
///
|
||||
/// The callback should return true if the key event has been handled by the
|
||||
@@ -59,6 +56,11 @@ typedef PlatformConfigurationChangedCallback = void Function(PlatformConfigurati
|
||||
// A gesture setting value that indicates it has not been set by the engine.
|
||||
const double _kUnsetGestureSetting = -1.0;
|
||||
|
||||
// A message channel to receive KeyData from the platform.
|
||||
//
|
||||
// See embedder.cc::kFlutterKeyDataChannel for more information.
|
||||
const String _kFlutterKeyDataChannel = 'flutter/keydata';
|
||||
|
||||
/// Platform event dispatcher singleton.
|
||||
///
|
||||
/// The most basic interface to the host operating system's interface.
|
||||
@@ -349,9 +351,19 @@ class PlatformDispatcher {
|
||||
return PointerDataPacket(data: data);
|
||||
}
|
||||
|
||||
/// Called by [_dispatchKeyData].
|
||||
void _respondToKeyData(int responseId, bool handled)
|
||||
native 'PlatformConfiguration_respondToKeyData';
|
||||
static ChannelCallback _keyDataListener(KeyDataCallback onKeyData, Zone zone) =>
|
||||
(ByteData? packet, PlatformMessageResponseCallback callback) {
|
||||
_invoke1<KeyData>(
|
||||
(KeyData keyData) {
|
||||
final bool handled = onKeyData(keyData);
|
||||
final Uint8List response = Uint8List(1);
|
||||
response[0] = handled ? 1 : 0;
|
||||
callback(response.buffer.asByteData());
|
||||
},
|
||||
zone,
|
||||
_unpackKeyData(packet!),
|
||||
);
|
||||
};
|
||||
|
||||
/// A callback that is invoked when key data is available.
|
||||
///
|
||||
@@ -362,22 +374,13 @@ class PlatformDispatcher {
|
||||
/// framework and should not be propagated further.
|
||||
KeyDataCallback? get onKeyData => _onKeyData;
|
||||
KeyDataCallback? _onKeyData;
|
||||
Zone _onKeyDataZone = Zone.root;
|
||||
set onKeyData(KeyDataCallback? callback) {
|
||||
_onKeyData = callback;
|
||||
_onKeyDataZone = Zone.current;
|
||||
}
|
||||
|
||||
// Called from the engine, via hooks.dart
|
||||
void _dispatchKeyData(ByteData packet, int responseId) {
|
||||
_invoke2<KeyData, _KeyDataResponseCallback>(
|
||||
(KeyData data, _KeyDataResponseCallback callback) {
|
||||
callback(responseId, onKeyData != null && onKeyData!(data));
|
||||
},
|
||||
_onKeyDataZone,
|
||||
_unpackKeyData(packet),
|
||||
_respondToKeyData,
|
||||
);
|
||||
if (callback != null) {
|
||||
channelBuffers.setListener(_kFlutterKeyDataChannel, _keyDataListener(callback, Zone.current));
|
||||
} else {
|
||||
channelBuffers.clearListener(_kFlutterKeyDataChannel);
|
||||
}
|
||||
}
|
||||
|
||||
// If this value changes, update the encoding code in the following files:
|
||||
|
||||
@@ -181,15 +181,6 @@ void GetPersistentIsolateData(Dart_NativeArguments args) {
|
||||
persistent_isolate_data->GetSize()));
|
||||
}
|
||||
|
||||
void RespondToKeyData(Dart_Handle window, int response_id, bool handled) {
|
||||
UIDartState::Current()->platform_configuration()->CompleteKeyDataResponse(
|
||||
response_id, handled);
|
||||
}
|
||||
|
||||
void _RespondToKeyData(Dart_NativeArguments args) {
|
||||
tonic::DartCallStatic(&RespondToKeyData, args);
|
||||
}
|
||||
|
||||
Dart_Handle ToByteData(const fml::Mapping& buffer) {
|
||||
return tonic::DartByteData::Create(buffer.GetMapping(), buffer.GetSize());
|
||||
}
|
||||
@@ -360,13 +351,6 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t id,
|
||||
args_handle}));
|
||||
}
|
||||
|
||||
uint64_t PlatformConfiguration::RegisterKeyDataResponse(
|
||||
KeyDataResponse callback) {
|
||||
uint64_t response_id = next_key_response_id_++;
|
||||
pending_key_responses_[response_id] = std::move(callback);
|
||||
return response_id;
|
||||
}
|
||||
|
||||
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,
|
||||
uint64_t frame_number) {
|
||||
std::shared_ptr<tonic::DartState> dart_state =
|
||||
@@ -444,27 +428,6 @@ void PlatformConfiguration::CompletePlatformMessageResponse(
|
||||
response->Complete(std::make_unique<fml::DataMapping>(std::move(data)));
|
||||
}
|
||||
|
||||
void PlatformConfiguration::CompleteKeyDataResponse(uint64_t response_id,
|
||||
bool handled) {
|
||||
if (response_id == 0) {
|
||||
return;
|
||||
}
|
||||
auto it = pending_key_responses_.find(response_id);
|
||||
FML_DCHECK(it != pending_key_responses_.end());
|
||||
if (it == pending_key_responses_.end()) {
|
||||
return;
|
||||
}
|
||||
KeyDataResponse callback = std::move(it->second);
|
||||
pending_key_responses_.erase(it);
|
||||
// The key result callback must be called on the platform thread. This is
|
||||
// mainly because iOS needs to block the platform message loop with a nested
|
||||
// loop to respond to key events synchronously, and if the callback is called
|
||||
// from another thread, it won't stop the nested message loop, causing a
|
||||
// deadlock.
|
||||
UIDartState::Current()->GetTaskRunners().GetPlatformTaskRunner()->PostTask(
|
||||
[handled, callback]() { callback(handled); });
|
||||
}
|
||||
|
||||
Dart_Handle ComputePlatformResolvedLocale(Dart_Handle supportedLocalesHandle) {
|
||||
std::vector<std::string> supportedLocales =
|
||||
tonic::DartConverter<std::vector<std::string>>::FromDart(
|
||||
@@ -495,7 +458,6 @@ void PlatformConfiguration::RegisterNatives(
|
||||
true},
|
||||
{"PlatformConfiguration_respondToPlatformMessage",
|
||||
_RespondToPlatformMessage, 3, true},
|
||||
{"PlatformConfiguration_respondToKeyData", _RespondToKeyData, 3, true},
|
||||
{"PlatformConfiguration_render", Render, 3, true},
|
||||
{"PlatformConfiguration_updateSemantics", UpdateSemantics, 2, true},
|
||||
{"PlatformConfiguration_setIsolateDebugName", SetIsolateDebugName, 2,
|
||||
|
||||
@@ -23,8 +23,6 @@ class FontCollection;
|
||||
class PlatformMessage;
|
||||
class Scene;
|
||||
|
||||
typedef std::function<void(bool /* handled */)> KeyDataResponse;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief An enum for defining the different kinds of accessibility features
|
||||
/// that can be enabled by the platform.
|
||||
@@ -327,24 +325,6 @@ class PlatformConfiguration final {
|
||||
SemanticsAction action,
|
||||
fml::MallocMapping args);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Registers a callback to be invoked when the framework has
|
||||
/// decided whether to handle an event. This callback originates
|
||||
/// in the platform view and has been forwarded through the engine
|
||||
/// to here.
|
||||
///
|
||||
/// This method will move and store the `callback`, associate it
|
||||
/// with a self-incrementing identifier, the response ID, then
|
||||
/// return the ID, which is typically used by
|
||||
/// Window::DispatchKeyDataPacket.
|
||||
///
|
||||
/// @param[in] callback The callback to be registered.
|
||||
///
|
||||
/// @return The response ID to be associated with the callback. Using this
|
||||
/// ID in CompleteKeyDataResponse will invoke the callback.
|
||||
///
|
||||
uint64_t RegisterKeyDataResponse(KeyDataResponse callback);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Notifies the framework that it is time to begin working on a
|
||||
/// new frame previously scheduled via a call to
|
||||
@@ -439,21 +419,6 @@ class PlatformConfiguration final {
|
||||
///
|
||||
void CompletePlatformMessageEmptyResponse(int response_id);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Responds to a previously registered key data message from the
|
||||
/// framework to the engine.
|
||||
///
|
||||
/// For each response_id, this method should be called exactly
|
||||
/// once. Responding to a response_id that has not been registered
|
||||
/// or has been invoked will lead to a fatal error.
|
||||
///
|
||||
/// @param[in] response_id The unique id that identifies the original platform
|
||||
/// message to respond to, created by
|
||||
/// RegisterKeyDataResponse.
|
||||
/// @param[in] handled Whether the key data is handled.
|
||||
///
|
||||
void CompleteKeyDataResponse(uint64_t response_id, bool handled);
|
||||
|
||||
private:
|
||||
PlatformConfigurationClient* client_;
|
||||
tonic::DartPersistentValue update_locales_;
|
||||
@@ -462,7 +427,6 @@ class PlatformConfiguration final {
|
||||
tonic::DartPersistentValue update_semantics_enabled_;
|
||||
tonic::DartPersistentValue update_accessibility_features_;
|
||||
tonic::DartPersistentValue dispatch_platform_message_;
|
||||
tonic::DartPersistentValue dispatch_key_message_;
|
||||
tonic::DartPersistentValue dispatch_semantics_action_;
|
||||
tonic::DartPersistentValue begin_frame_;
|
||||
tonic::DartPersistentValue draw_frame_;
|
||||
@@ -474,10 +438,6 @@ class PlatformConfiguration final {
|
||||
int next_response_id_ = 1;
|
||||
std::unordered_map<int, fml::RefPtr<PlatformMessageResponse>>
|
||||
pending_responses_;
|
||||
|
||||
// ID starts at 1 because an ID of 0 indicates that no response is expected.
|
||||
uint64_t next_key_response_id_ = 1;
|
||||
std::unordered_map<uint64_t, KeyDataResponse> pending_key_responses_;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@@ -36,25 +36,6 @@ void Window::DispatchPointerDataPacket(const PointerDataPacket& packet) {
|
||||
library_.value(), "_dispatchPointerDataPacket", {data_handle}));
|
||||
}
|
||||
|
||||
void Window::DispatchKeyDataPacket(const KeyDataPacket& packet,
|
||||
uint64_t response_id) {
|
||||
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
|
||||
if (!dart_state) {
|
||||
return;
|
||||
}
|
||||
tonic::DartState::Scope scope(dart_state);
|
||||
|
||||
const std::vector<uint8_t>& buffer = packet.data();
|
||||
Dart_Handle data_handle =
|
||||
tonic::DartByteData::Create(buffer.data(), buffer.size());
|
||||
if (Dart_IsError(data_handle)) {
|
||||
return;
|
||||
}
|
||||
tonic::LogIfError(
|
||||
tonic::DartInvokeField(library_.value(), "_dispatchKeyData",
|
||||
{data_handle, tonic::ToDart(response_id)}));
|
||||
}
|
||||
|
||||
void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) {
|
||||
viewport_metrics_ = metrics;
|
||||
|
||||
|
||||
@@ -31,14 +31,6 @@ class Window final {
|
||||
// Dispatch a packet to the framework that indicates one or a few pointer
|
||||
// events.
|
||||
void DispatchPointerDataPacket(const PointerDataPacket& packet);
|
||||
// Dispatch a packet to the framework that indicates a key event.
|
||||
//
|
||||
// The `response_id` is used to label the response of whether the key event
|
||||
// is handled by the framework, typically the return value of
|
||||
// PlatformConfiguration::RegisterKeyDataResponse.
|
||||
// It should be used later in
|
||||
// PlatformConfiguration::CompleteKeyDataResponse.
|
||||
void DispatchKeyDataPacket(const KeyDataPacket& packet, uint64_t response_id);
|
||||
void UpdateWindowMetrics(const ViewportMetrics& metrics);
|
||||
|
||||
private:
|
||||
|
||||
@@ -238,20 +238,6 @@ bool RuntimeController::DispatchPointerDataPacket(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeController::DispatchKeyDataPacket(const KeyDataPacket& packet,
|
||||
KeyDataResponse callback) {
|
||||
if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) {
|
||||
TRACE_EVENT1("flutter", "RuntimeController::DispatchKeyDataPacket", "mode",
|
||||
"basic");
|
||||
uint64_t response_id =
|
||||
platform_configuration->RegisterKeyDataResponse(std::move(callback));
|
||||
platform_configuration->get_window(0)->DispatchKeyDataPacket(packet,
|
||||
response_id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeController::DispatchSemanticsAction(int32_t id,
|
||||
SemanticsAction action,
|
||||
fml::MallocMapping args) {
|
||||
|
||||
@@ -385,20 +385,6 @@ class RuntimeController : public PlatformConfigurationClient {
|
||||
///
|
||||
bool DispatchPointerDataPacket(const PointerDataPacket& packet);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Dispatch the specified pointer data message to the running
|
||||
/// root isolate.
|
||||
///
|
||||
/// @param[in] packet The key data message to dispatch to the isolate.
|
||||
/// @param[in] callback Called when the framework has decided whether
|
||||
/// to handle this key data.
|
||||
///
|
||||
/// @return If the key data message was dispatched. This may fail is
|
||||
/// an isolate is not running.
|
||||
///
|
||||
bool DispatchKeyDataPacket(const KeyDataPacket& packet,
|
||||
KeyDataResponse callback);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Dispatch the semantics action to the specified accessibility
|
||||
/// node.
|
||||
|
||||
@@ -434,14 +434,6 @@ void Engine::DispatchPointerDataPacket(
|
||||
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id);
|
||||
}
|
||||
|
||||
void Engine::DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
KeyDataResponse callback) {
|
||||
TRACE_EVENT0("flutter", "Engine::DispatchKeyDataPacket");
|
||||
if (runtime_controller_) {
|
||||
runtime_controller_->DispatchKeyDataPacket(*packet, std::move(callback));
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::DispatchSemanticsAction(int id,
|
||||
SemanticsAction action,
|
||||
fml::MallocMapping args) {
|
||||
|
||||
@@ -735,21 +735,6 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
void DispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet,
|
||||
uint64_t trace_flow_id);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Notifies the engine that the embedder has sent it a key data
|
||||
/// packet. A key data packet contains one key event. This call
|
||||
/// originates in the platform view and the shell has forwarded
|
||||
/// the same to the engine on the UI task runner here. The engine
|
||||
/// will decide whether to handle this event, and send the
|
||||
/// result using `callback`, which will be called exactly once.
|
||||
///
|
||||
/// @param[in] packet The key data packet.
|
||||
/// @param[in] callback Called when the framework has decided whether
|
||||
/// to handle this key data.
|
||||
///
|
||||
void DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
KeyDataResponse callback);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Notifies the engine that the embedder encountered an
|
||||
/// accessibility related action on the specified node. This call
|
||||
|
||||
@@ -39,12 +39,6 @@ void PlatformView::DispatchPointerDataPacket(
|
||||
pointer_data_packet_converter_.Convert(std::move(packet)));
|
||||
}
|
||||
|
||||
void PlatformView::DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
KeyDataResponse callback) {
|
||||
delegate_.OnPlatformViewDispatchKeyDataPacket(std::move(packet),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void PlatformView::DispatchSemanticsAction(int32_t id,
|
||||
SemanticsAction action,
|
||||
fml::MallocMapping args) {
|
||||
|
||||
@@ -127,20 +127,6 @@ class PlatformView {
|
||||
virtual void OnPlatformViewDispatchPointerDataPacket(
|
||||
std::unique_ptr<PointerDataPacket> packet) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Notifies the delegate that the platform view has encountered
|
||||
/// a key event. This key event and the callback needs to be
|
||||
/// forwarded to the running root isolate hosted by the engine
|
||||
/// on the UI thread.
|
||||
///
|
||||
/// @param[in] packet The key data packet containing one key event.
|
||||
/// @param[in] callback Called when the framework has decided whether
|
||||
/// to handle this key data.
|
||||
///
|
||||
virtual void OnPlatformViewDispatchKeyDataPacket(
|
||||
std::unique_ptr<KeyDataPacket> packet,
|
||||
std::function<void(bool /* handled */)> callback) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Notifies the delegate that the platform view has encountered
|
||||
/// an accessibility related action on the specified node. This
|
||||
@@ -591,17 +577,6 @@ class PlatformView {
|
||||
///
|
||||
void DispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Dispatches key events from the embedder to the framework. Each
|
||||
/// key data packet contains one physical event and multiple
|
||||
/// logical key events. Each call to this method wakes up the UI
|
||||
/// thread.
|
||||
///
|
||||
/// @param[in] packet The key data packet to dispatch to the framework.
|
||||
///
|
||||
void DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
Delegate::KeyDataResponse callback);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Used by the embedder to specify a texture that it wants the
|
||||
/// rasterizer to composite within the Flutter layer tree. All
|
||||
|
||||
@@ -942,23 +942,6 @@ void Shell::OnPlatformViewDispatchPointerDataPacket(
|
||||
next_pointer_flow_id_++;
|
||||
}
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
void Shell::OnPlatformViewDispatchKeyDataPacket(
|
||||
std::unique_ptr<KeyDataPacket> packet,
|
||||
std::function<void(bool /* handled */)> callback) {
|
||||
TRACE_EVENT0("flutter", "Shell::OnPlatformViewDispatchKeyDataPacket");
|
||||
FML_DCHECK(is_setup_);
|
||||
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
||||
|
||||
task_runners_.GetUITaskRunner()->PostTask(
|
||||
fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet),
|
||||
callback = std::move(callback)]() mutable {
|
||||
if (engine) {
|
||||
engine->DispatchKeyDataPacket(std::move(packet), std::move(callback));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
void Shell::OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||
SemanticsAction action,
|
||||
|
||||
@@ -525,11 +525,6 @@ class Shell final : public PlatformView::Delegate,
|
||||
void OnPlatformViewDispatchPointerDataPacket(
|
||||
std::unique_ptr<PointerDataPacket> packet) override;
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
void OnPlatformViewDispatchKeyDataPacket(
|
||||
std::unique_ptr<KeyDataPacket> packet,
|
||||
std::function<void(bool /* handled */)> callback) override;
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||
SemanticsAction action,
|
||||
|
||||
@@ -67,10 +67,6 @@ class MockPlatformViewDelegate : public PlatformView::Delegate {
|
||||
MOCK_METHOD1(OnPlatformViewDispatchPointerDataPacket,
|
||||
void(std::unique_ptr<PointerDataPacket> packet));
|
||||
|
||||
MOCK_METHOD2(OnPlatformViewDispatchKeyDataPacket,
|
||||
void(std::unique_ptr<KeyDataPacket> packet,
|
||||
KeyDataResponse callback));
|
||||
|
||||
MOCK_METHOD3(OnPlatformViewDispatchSemanticsAction,
|
||||
void(int32_t id,
|
||||
SemanticsAction action,
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
NSString* const FlutterDefaultDartEntrypoint = nil;
|
||||
NSString* const FlutterDefaultInitialRoute = nil;
|
||||
NSString* const FlutterEngineWillDealloc = @"FlutterEngineWillDealloc";
|
||||
NSString* const FlutterKeyDataChannel = @"flutter/keydata";
|
||||
static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
|
||||
@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
|
||||
@@ -301,13 +302,20 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
key_data.synthesized = event.synthesized;
|
||||
|
||||
auto packet = std::make_unique<flutter::KeyDataPacket>(key_data, character);
|
||||
NSData* message = [NSData dataWithBytes:packet->data().data() length:packet->data().size()];
|
||||
|
||||
auto response = [callback, userData](bool handled) {
|
||||
if (callback != nullptr) {
|
||||
callback(handled, userData);
|
||||
auto response = ^(NSData* reply) {
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
BOOL handled = FALSE;
|
||||
if (reply.length == 1 && *reinterpret_cast<const uint8_t*>(reply.bytes) == 1) {
|
||||
handled = TRUE;
|
||||
}
|
||||
callback(handled, userData);
|
||||
};
|
||||
self.platformView->DispatchKeyDataPacket(std::move(packet), std::move(response));
|
||||
|
||||
[self sendOnChannel:FlutterKeyDataChannel message:message binaryReply:response];
|
||||
}
|
||||
|
||||
- (void)ensureSemanticsEnabled {
|
||||
|
||||
@@ -26,8 +26,6 @@ class FakeDelegate : public PlatformView::Delegate {
|
||||
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||
}
|
||||
void OnPlatformViewDispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
std::function<void(bool)> callback) override {}
|
||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||
SemanticsAction action,
|
||||
fml::MallocMapping args) override {}
|
||||
|
||||
@@ -95,8 +95,6 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De
|
||||
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||
}
|
||||
void OnPlatformViewDispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
std::function<void(bool)> callback) override {}
|
||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||
SemanticsAction action,
|
||||
fml::MallocMapping args) override {}
|
||||
|
||||
@@ -79,8 +79,6 @@ class MockDelegate : public PlatformView::Delegate {
|
||||
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||
}
|
||||
void OnPlatformViewDispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
std::function<void(bool)> callback) override {}
|
||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||
SemanticsAction action,
|
||||
fml::MallocMapping args) override {}
|
||||
|
||||
@@ -90,9 +90,6 @@ class PlatformViewIOS final : public PlatformView {
|
||||
// |PlatformView|
|
||||
PointerDataDispatcherMaker GetDispatcherMaker() override;
|
||||
|
||||
void DispatchKeyDataPacket(std::unique_ptr<KeyDataPacket> packet,
|
||||
std::function<void(bool /* handled */)> callback);
|
||||
|
||||
// |PlatformView|
|
||||
void SetSemanticsEnabled(bool enabled) override;
|
||||
|
||||
|
||||
@@ -69,6 +69,26 @@ extern const intptr_t kPlatformStrongDillSize;
|
||||
const int32_t kFlutterSemanticsNodeIdBatchEnd = -1;
|
||||
const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1;
|
||||
|
||||
// A message channel to send platform-independent FlutterKeyData to the
|
||||
// framework.
|
||||
//
|
||||
// This should be kept in sync with the following variables:
|
||||
//
|
||||
// - lib/ui/platform_dispatcher.dart, _kFlutterKeyDataChannel
|
||||
// - shell/platform/darwin/ios/framework/Source/FlutterEngine.mm,
|
||||
// FlutterKeyDataChannel
|
||||
//
|
||||
// Not to be confused with "flutter/keyevent", which is used to send raw
|
||||
// key event data in a platform-dependent format.
|
||||
//
|
||||
// ## Format
|
||||
//
|
||||
// Send: KeyDataPacket.data().
|
||||
//
|
||||
// Expected reply: Whether the event is handled. Exactly 1 byte long, with value
|
||||
// 1 for handled, and 0 for not. Malformed value is considered false.
|
||||
const char* kFlutterKeyDataChannel = "flutter/keydata";
|
||||
|
||||
static FlutterEngineResult LogEmbedderError(FlutterEngineResult code,
|
||||
const char* reason,
|
||||
const char* code_name,
|
||||
@@ -1593,6 +1613,45 @@ static inline flutter::KeyEventType MapKeyEventType(
|
||||
return flutter::KeyEventType::kUp;
|
||||
}
|
||||
|
||||
// Send a platform message to the framework.
|
||||
//
|
||||
// The `data_callback` will be invoked with `user_data`, and must not be empty.
|
||||
static FlutterEngineResult InternalSendPlatformMessage(
|
||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||
const char* channel,
|
||||
const uint8_t* data,
|
||||
size_t size,
|
||||
FlutterDataCallback data_callback,
|
||||
void* user_data) {
|
||||
FlutterEngineResult result;
|
||||
|
||||
FlutterPlatformMessageResponseHandle* response_handle;
|
||||
result = FlutterPlatformMessageCreateResponseHandle(
|
||||
engine, data_callback, user_data, &response_handle);
|
||||
if (result != kSuccess) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const FlutterPlatformMessage message = {
|
||||
sizeof(FlutterPlatformMessage), // struct_size
|
||||
channel, // channel
|
||||
data, // message
|
||||
size, // message_size
|
||||
response_handle, // response_handle
|
||||
};
|
||||
|
||||
result = FlutterEngineSendPlatformMessage(engine, &message);
|
||||
// Whether `SendPlatformMessage` succeeds or not, the response handle must be
|
||||
// released.
|
||||
FlutterEngineResult release_result =
|
||||
FlutterPlatformMessageReleaseResponseHandle(engine, response_handle);
|
||||
if (result != kSuccess) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return release_result;
|
||||
}
|
||||
|
||||
FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||
engine,
|
||||
const FlutterKeyEvent* event,
|
||||
@@ -1619,18 +1678,27 @@ FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine)
|
||||
|
||||
auto packet = std::make_unique<flutter::KeyDataPacket>(key_data, character);
|
||||
|
||||
auto response = [callback, user_data](bool handled) {
|
||||
if (callback != nullptr) {
|
||||
callback(handled, user_data);
|
||||
}
|
||||
struct MessageData {
|
||||
FlutterKeyEventCallback callback;
|
||||
void* user_data;
|
||||
};
|
||||
|
||||
return reinterpret_cast<flutter::EmbedderEngine*>(engine)
|
||||
->DispatchKeyDataPacket(std::move(packet), response)
|
||||
? kSuccess
|
||||
: LOG_EMBEDDER_ERROR(kInternalInconsistency,
|
||||
"Could not dispatch the key event to the "
|
||||
"running Flutter application.");
|
||||
MessageData* message_data =
|
||||
new MessageData{.callback = callback, .user_data = user_data};
|
||||
|
||||
return InternalSendPlatformMessage(
|
||||
engine, kFlutterKeyDataChannel, packet->data().data(),
|
||||
packet->data().size(),
|
||||
[](const uint8_t* data, size_t size, void* user_data) {
|
||||
auto message_data = std::unique_ptr<MessageData>(
|
||||
reinterpret_cast<MessageData*>(user_data));
|
||||
bool handled = false;
|
||||
if (size == 1) {
|
||||
handled = *data != 0;
|
||||
}
|
||||
message_data->callback(handled, message_data->user_data);
|
||||
},
|
||||
message_data);
|
||||
}
|
||||
|
||||
FlutterEngineResult FlutterEngineSendPlatformMessage(
|
||||
|
||||
@@ -127,22 +127,6 @@ bool EmbedderEngine::DispatchPointerDataPacket(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmbedderEngine::DispatchKeyDataPacket(
|
||||
std::unique_ptr<flutter::KeyDataPacket> packet,
|
||||
KeyDataResponse callback) {
|
||||
if (!IsValid() || !packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto platform_view = shell_->GetPlatformView();
|
||||
if (!platform_view) {
|
||||
return false;
|
||||
}
|
||||
|
||||
platform_view->DispatchKeyDataPacket(std::move(packet), std::move(callback));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmbedderEngine::SendPlatformMessage(
|
||||
std::unique_ptr<PlatformMessage> message) {
|
||||
if (!IsValid() || !message) {
|
||||
|
||||
@@ -52,22 +52,6 @@ class EmbedderEngine {
|
||||
bool DispatchPointerDataPacket(
|
||||
std::unique_ptr<flutter::PointerDataPacket> packet);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Notifies the platform view that the embedder has sent it a key
|
||||
/// data packet. A key data packet contains one key event. This
|
||||
/// call originates in the platform view and the shell has
|
||||
/// forwarded the same to the engine on the UI task runner here.
|
||||
/// The platform view will decide whether to handle this event,
|
||||
/// and send the result using `callback`, which will be called
|
||||
/// exactly once.
|
||||
///
|
||||
/// @param[in] packet The key data packet.
|
||||
/// @param[in] callback Called when the framework has decided whether
|
||||
/// to handle this key data.
|
||||
///
|
||||
bool DispatchKeyDataPacket(std::unique_ptr<flutter::KeyDataPacket> packet,
|
||||
KeyDataResponse callback);
|
||||
|
||||
bool SendPlatformMessage(std::unique_ptr<PlatformMessage> message);
|
||||
|
||||
bool RegisterTexture(int64_t texture);
|
||||
|
||||
@@ -567,6 +567,27 @@ void key_data_echo() async {
|
||||
signalNativeTest();
|
||||
}
|
||||
|
||||
// After platform channel 'test/starts_echo' receives a message, starts echoing
|
||||
// the event data with `_echoKeyEvent`, and returns synthesized as handled.
|
||||
@pragma('vm:entry-point')
|
||||
void key_data_late_echo() async {
|
||||
channelBuffers.setListener('test/starts_echo', (ByteData? data, PlatformMessageResponseCallback callback) {
|
||||
PlatformDispatcher.instance.onKeyData = (KeyData data) {
|
||||
_echoKeyEvent(
|
||||
_serializeKeyEventType(data.type),
|
||||
data.timeStamp.inMicroseconds,
|
||||
data.physical,
|
||||
data.logical,
|
||||
data.character == null ? 0 : data.character!.codeUnitAt(0),
|
||||
data.synthesized,
|
||||
);
|
||||
return data.synthesized;
|
||||
};
|
||||
callback(null);
|
||||
});
|
||||
signalNativeTest();
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void render_gradient() {
|
||||
PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
|
||||
|
||||
@@ -1355,7 +1355,7 @@ TEST_F(EmbedderTest, KeyDataIsCorrectlySerialized) {
|
||||
echoed_event.type =
|
||||
UnserializeKeyEventKind(tonic::DartConverter<uint64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 0)));
|
||||
echoed_event.timestamp = tonic::DartConverter<uint64_t>::FromDart(
|
||||
echoed_event.timestamp = (double)tonic::DartConverter<uint64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 1));
|
||||
echoed_event.physical = tonic::DartConverter<uint64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 2));
|
||||
@@ -1440,6 +1440,99 @@ TEST_F(EmbedderTest, KeyDataIsCorrectlySerialized) {
|
||||
EXPECT_EQ(echoed_char, 0llu);
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, KeyDataAreBuffered) {
|
||||
auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>();
|
||||
std::vector<FlutterKeyEvent> echoed_events;
|
||||
|
||||
auto native_echo_event = [&](Dart_NativeArguments args) {
|
||||
echoed_events.push_back(FlutterKeyEvent{
|
||||
.timestamp = (double)tonic::DartConverter<uint64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 1)),
|
||||
.type =
|
||||
UnserializeKeyEventKind(tonic::DartConverter<uint64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 0))),
|
||||
.physical = tonic::DartConverter<uint64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 2)),
|
||||
.logical = tonic::DartConverter<uint64_t>::FromDart(
|
||||
Dart_GetNativeArgument(args, 3)),
|
||||
.synthesized = tonic::DartConverter<bool>::FromDart(
|
||||
Dart_GetNativeArgument(args, 5)),
|
||||
});
|
||||
|
||||
message_latch->Signal();
|
||||
};
|
||||
|
||||
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
|
||||
EmbedderConfigBuilder builder(context);
|
||||
builder.SetSoftwareRendererConfig();
|
||||
builder.SetDartEntrypoint("key_data_late_echo");
|
||||
fml::AutoResetWaitableEvent ready;
|
||||
context.AddNativeCallback(
|
||||
"SignalNativeTest",
|
||||
CREATE_NATIVE_ENTRY(
|
||||
[&ready](Dart_NativeArguments args) { ready.Signal(); }));
|
||||
|
||||
context.AddNativeCallback("EchoKeyEvent",
|
||||
CREATE_NATIVE_ENTRY(native_echo_event));
|
||||
|
||||
auto engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
ready.Wait();
|
||||
|
||||
FlutterKeyEvent sample_event{
|
||||
.struct_size = sizeof(FlutterKeyEvent),
|
||||
.type = kFlutterKeyEventTypeDown,
|
||||
.physical = 0x00070004,
|
||||
.logical = 0x00000000061,
|
||||
.character = "A",
|
||||
.synthesized = false,
|
||||
};
|
||||
|
||||
// Send an event.
|
||||
sample_event.timestamp = 1.0l;
|
||||
FlutterEngineSendKeyEvent(
|
||||
engine.get(), &sample_event, [](bool handled, void* user_data) {},
|
||||
nullptr);
|
||||
|
||||
// Should not receive echos because the callback is not set yet.
|
||||
EXPECT_EQ(echoed_events.size(), 0u);
|
||||
|
||||
// Send an empty message to 'test/starts_echo' to start echoing.
|
||||
FlutterPlatformMessageResponseHandle* response_handle = nullptr;
|
||||
FlutterPlatformMessageCreateResponseHandle(
|
||||
engine.get(), [](const uint8_t* data, size_t size, void* user_data) {},
|
||||
nullptr, &response_handle);
|
||||
|
||||
FlutterPlatformMessage message{
|
||||
.struct_size = sizeof(FlutterPlatformMessage),
|
||||
.channel = "test/starts_echo",
|
||||
.message = nullptr,
|
||||
.message_size = 0,
|
||||
.response_handle = response_handle,
|
||||
};
|
||||
|
||||
FlutterEngineResult result =
|
||||
FlutterEngineSendPlatformMessage(engine.get(), &message);
|
||||
ASSERT_EQ(result, kSuccess);
|
||||
|
||||
FlutterPlatformMessageReleaseResponseHandle(engine.get(), response_handle);
|
||||
|
||||
// message_latch->Wait();
|
||||
message_latch->Wait();
|
||||
// All previous events should be received now.
|
||||
EXPECT_EQ(echoed_events.size(), 1u);
|
||||
|
||||
// Send a second event.
|
||||
sample_event.timestamp = 10.0l;
|
||||
FlutterEngineSendKeyEvent(
|
||||
engine.get(), &sample_event, [](bool handled, void* user_data) {},
|
||||
nullptr);
|
||||
message_latch->Wait();
|
||||
|
||||
// The event should be echoed, too.
|
||||
EXPECT_EQ(echoed_events.size(), 2u);
|
||||
}
|
||||
|
||||
TEST_F(EmbedderTest, KeyDataResponseIsCorrectlyInvoked) {
|
||||
UniqueEngine engine;
|
||||
fml::AutoResetWaitableEvent sync_latch;
|
||||
|
||||
@@ -102,10 +102,6 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
|
||||
pointer_packets_.push_back(std::move(packet));
|
||||
}
|
||||
// |flutter::PlatformView::Delegate|
|
||||
void OnPlatformViewDispatchKeyDataPacket(
|
||||
std::unique_ptr<flutter::KeyDataPacket> packet,
|
||||
std::function<void(bool)> callback) {}
|
||||
// |flutter::PlatformView::Delegate|
|
||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
||||
flutter::SemanticsAction action,
|
||||
fml::MallocMapping args) {}
|
||||
|
||||
Reference in New Issue
Block a user