diff --git a/engine/src/flutter/lib/ui/fixtures/ui_test.dart b/engine/src/flutter/lib/ui/fixtures/ui_test.dart index c59b3cc5ac..28962a7884 100644 --- a/engine/src/flutter/lib/ui/fixtures/ui_test.dart +++ b/engine/src/flutter/lib/ui/fixtures/ui_test.dart @@ -1060,8 +1060,9 @@ void hooksTests() async { }; }); - _callHook('_dispatchSemanticsAction', 3, 1234, 4, null); + _callHook('_dispatchSemanticsAction', 4, 456, 1234, 4, null); expectIdentical(runZone, innerZone); + expectEquals(action.viewId, 456); expectEquals(action.nodeId, 1234); expectEquals(action.type.index, 4); }); diff --git a/engine/src/flutter/lib/ui/hooks.dart b/engine/src/flutter/lib/ui/hooks.dart index c053eb27e8..d79a0eb4d4 100644 --- a/engine/src/flutter/lib/ui/hooks.dart +++ b/engine/src/flutter/lib/ui/hooks.dart @@ -283,8 +283,8 @@ void _dispatchPointerDataPacket(ByteData packet) { } @pragma('vm:entry-point') -void _dispatchSemanticsAction(int nodeId, int action, ByteData? args) { - PlatformDispatcher.instance._dispatchSemanticsAction(nodeId, action, args); +void _dispatchSemanticsAction(int viewId, int nodeId, int action, ByteData? args) { + PlatformDispatcher.instance._dispatchSemanticsAction(viewId, nodeId, action, args); } @pragma('vm:entry-point') diff --git a/engine/src/flutter/lib/ui/platform_dispatcher.dart b/engine/src/flutter/lib/ui/platform_dispatcher.dart index d2a42974f4..3d65c6b6ce 100644 --- a/engine/src/flutter/lib/ui/platform_dispatcher.dart +++ b/engine/src/flutter/lib/ui/platform_dispatcher.dart @@ -939,10 +939,12 @@ class PlatformDispatcher { call `updateSemantics`. ''') void updateSemantics(SemanticsUpdate update) => - _updateSemantics(update as _NativeSemanticsUpdate); + _updateSemantics(_implicitViewId!, update as _NativeSemanticsUpdate); - @Native)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics') - external static void _updateSemantics(_NativeSemanticsUpdate update); + @Native)>( + symbol: 'PlatformConfigurationNativeApi::UpdateSemantics', + ) + external static void _updateSemantics(int viewId, _NativeSemanticsUpdate update); /// The system-reported default locale of the device. /// @@ -1332,14 +1334,14 @@ class PlatformDispatcher { } // Called from the engine, via hooks.dart - void _dispatchSemanticsAction(int nodeId, int action, ByteData? args) { + void _dispatchSemanticsAction(int viewId, int nodeId, int action, ByteData? args) { _invoke1( onSemanticsActionEvent, _onSemanticsActionEventZone, SemanticsActionEvent( type: SemanticsAction.fromIndex(action)!, nodeId: nodeId, - viewId: 0, // TODO(goderbauer): Wire up the real view ID. + viewId: viewId, arguments: args, ), ); diff --git a/engine/src/flutter/lib/ui/window.dart b/engine/src/flutter/lib/ui/window.dart index 3f7aecfbf0..48a18692cf 100644 --- a/engine/src/flutter/lib/ui/window.dart +++ b/engine/src/flutter/lib/ui/window.dart @@ -397,10 +397,12 @@ class FlutterView { /// This function disposes the given update, which means the semantics update /// cannot be used further. void updateSemantics(SemanticsUpdate update) => - _updateSemantics(update as _NativeSemanticsUpdate); + _updateSemantics(viewId, update as _NativeSemanticsUpdate); - @Native)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics') - external static void _updateSemantics(_NativeSemanticsUpdate update); + @Native)>( + symbol: 'PlatformConfigurationNativeApi::UpdateSemantics', + ) + external static void _updateSemantics(int viewId, _NativeSemanticsUpdate update); @override String toString() => 'FlutterView(id: $viewId)'; diff --git a/engine/src/flutter/lib/ui/window/platform_configuration.cc b/engine/src/flutter/lib/ui/window/platform_configuration.cc index 654ac77c9f..c47a45ea0a 100644 --- a/engine/src/flutter/lib/ui/window/platform_configuration.cc +++ b/engine/src/flutter/lib/ui/window/platform_configuration.cc @@ -380,7 +380,8 @@ void PlatformConfiguration::DispatchPointerDataPacket( tonic::DartInvoke(dispatch_pointer_data_packet_.Get(), {data_handle})); } -void PlatformConfiguration::DispatchSemanticsAction(int32_t node_id, +void PlatformConfiguration::DispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) { std::shared_ptr dart_state = @@ -399,8 +400,8 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t node_id, tonic::CheckAndHandleError(tonic::DartInvoke( dispatch_semantics_action_.Get(), - {tonic::ToDart(node_id), tonic::ToDart(static_cast(action)), - args_handle})); + {tonic::ToDart(view_id), tonic::ToDart(node_id), + tonic::ToDart(static_cast(action)), args_handle})); } void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime, @@ -661,10 +662,11 @@ void PlatformConfigurationNativeApi::EndWarmUpFrame() { UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame(); } -void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) { +void PlatformConfigurationNativeApi::UpdateSemantics(int64_t view_id, + SemanticsUpdate* update) { UIDartState::ThrowIfUIOperationsProhibited(); UIDartState::Current()->platform_configuration()->client()->UpdateSemantics( - update); + view_id, update); } Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale( diff --git a/engine/src/flutter/lib/ui/window/platform_configuration.h b/engine/src/flutter/lib/ui/window/platform_configuration.h index e1f3ba474e..29ee8ca4ff 100644 --- a/engine/src/flutter/lib/ui/window/platform_configuration.h +++ b/engine/src/flutter/lib/ui/window/platform_configuration.h @@ -92,9 +92,10 @@ class PlatformConfigurationClient { //-------------------------------------------------------------------------- /// @brief Receives an updated semantics tree from the Framework. /// + /// @param[in] viewId The identifier of the view to update. /// @param[in] update The updated semantic tree to apply. /// - virtual void UpdateSemantics(SemanticsUpdate* update) = 0; + virtual void UpdateSemantics(int64_t viewId, SemanticsUpdate* update) = 0; //-------------------------------------------------------------------------- /// @brief When the Flutter application has a message to send to the @@ -451,12 +452,14 @@ class PlatformConfiguration final { /// originates on the platform view and has been forwarded to the /// platform configuration here by the engine. /// + /// @param[in] view_id The identifier of the view. /// @param[in] node_id The identifier of the accessibility node. /// @param[in] action The accessibility related action performed on the /// node of the specified ID. /// @param[in] args Optional data that applies to the specified action. /// - void DispatchSemanticsAction(int32_t node_id, + void DispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args); @@ -620,7 +623,7 @@ class PlatformConfigurationNativeApi { double width, double height); - static void UpdateSemantics(SemanticsUpdate* update); + static void UpdateSemantics(int64_t viewId, SemanticsUpdate* update); static void SetNeedsReportTimings(bool value); diff --git a/engine/src/flutter/runtime/dart_isolate_unittests.cc b/engine/src/flutter/runtime/dart_isolate_unittests.cc index 0ddc20abf2..602011c1c5 100644 --- a/engine/src/flutter/runtime/dart_isolate_unittests.cc +++ b/engine/src/flutter/runtime/dart_isolate_unittests.cc @@ -711,7 +711,7 @@ class FakePlatformConfigurationClient : public PlatformConfigurationClient { Scene* scene, double width, double height) override {} - void UpdateSemantics(SemanticsUpdate* update) override {} + void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override {} void HandlePlatformMessage( std::unique_ptr message) override {} FontCollection& GetFontCollection() override { diff --git a/engine/src/flutter/runtime/runtime_controller.cc b/engine/src/flutter/runtime/runtime_controller.cc index 950a3623dc..113e1fb8c9 100644 --- a/engine/src/flutter/runtime/runtime_controller.cc +++ b/engine/src/flutter/runtime/runtime_controller.cc @@ -383,13 +383,14 @@ bool RuntimeController::DispatchPointerDataPacket( return false; } -bool RuntimeController::DispatchSemanticsAction(int32_t node_id, +bool RuntimeController::DispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) { TRACE_EVENT1("flutter", "RuntimeController::DispatchSemanticsAction", "mode", "basic"); if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - platform_configuration->DispatchSemanticsAction(node_id, action, + platform_configuration->DispatchSemanticsAction(view_id, node_id, action, std::move(args)); return true; } @@ -447,9 +448,11 @@ void RuntimeController::CheckIfAllViewsRendered() { } // |PlatformConfigurationClient| -void RuntimeController::UpdateSemantics(SemanticsUpdate* update) { +void RuntimeController::UpdateSemantics(int64_t view_id, + SemanticsUpdate* update) { if (platform_data_.semantics_enabled) { - client_.UpdateSemantics(update->takeNodes(), update->takeActions()); + client_.UpdateSemantics(view_id, update->takeNodes(), + update->takeActions()); } } diff --git a/engine/src/flutter/runtime/runtime_controller.h b/engine/src/flutter/runtime/runtime_controller.h index 243891f41b..5b11cf2b81 100644 --- a/engine/src/flutter/runtime/runtime_controller.h +++ b/engine/src/flutter/runtime/runtime_controller.h @@ -490,7 +490,8 @@ class RuntimeController : public PlatformConfigurationClient, /// @brief Dispatch the semantics action to the specified accessibility /// node. /// - /// @param[in] node_id The identified of the accessibility node. + /// @param[in] view_id The identifier of the view. + /// @param[in] node_id The identifier of the accessibility node. /// @param[in] action The semantics action to perform on the specified /// accessibility node. /// @param[in] args Optional data that applies to the specified action. @@ -498,7 +499,8 @@ class RuntimeController : public PlatformConfigurationClient, /// @return If the semantics action was dispatched. This may fail if an /// isolate is not running. /// - bool DispatchSemanticsAction(int32_t node_id, + bool DispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args); @@ -767,7 +769,7 @@ class RuntimeController : public PlatformConfigurationClient, double height) override; // |PlatformConfigurationClient| - void UpdateSemantics(SemanticsUpdate* update) override; + void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override; // |PlatformConfigurationClient| void HandlePlatformMessage(std::unique_ptr message) override; diff --git a/engine/src/flutter/runtime/runtime_delegate.h b/engine/src/flutter/runtime/runtime_delegate.h index 8e5b082b96..d5df459791 100644 --- a/engine/src/flutter/runtime/runtime_delegate.h +++ b/engine/src/flutter/runtime/runtime_delegate.h @@ -32,7 +32,8 @@ class RuntimeDelegate { std::unique_ptr layer_tree, float device_pixel_ratio) = 0; - virtual void UpdateSemantics(SemanticsNodeUpdates update, + virtual void UpdateSemantics(int64_t view_id, + SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) = 0; virtual void HandlePlatformMessage( diff --git a/engine/src/flutter/shell/common/engine.cc b/engine/src/flutter/shell/common/engine.cc index 911b1685df..56e894335a 100644 --- a/engine/src/flutter/shell/common/engine.cc +++ b/engine/src/flutter/shell/common/engine.cc @@ -450,10 +450,11 @@ void Engine::DispatchPointerDataPacket( pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id); } -void Engine::DispatchSemanticsAction(int node_id, +void Engine::DispatchSemanticsAction(int64_t view_id, + int node_id, SemanticsAction action, fml::MallocMapping args) { - runtime_controller_->DispatchSemanticsAction(node_id, action, + runtime_controller_->DispatchSemanticsAction(view_id, node_id, action, std::move(args)); } @@ -495,9 +496,11 @@ void Engine::Render(int64_t view_id, animator_->Render(view_id, std::move(layer_tree), device_pixel_ratio); } -void Engine::UpdateSemantics(SemanticsNodeUpdates update, +void Engine::UpdateSemantics(int64_t view_id, + SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) { - delegate_.OnEngineUpdateSemantics(std::move(update), std::move(actions)); + delegate_.OnEngineUpdateSemantics(view_id, std::move(update), + std::move(actions)); } void Engine::HandlePlatformMessage(std::unique_ptr message) { diff --git a/engine/src/flutter/shell/common/engine.h b/engine/src/flutter/shell/common/engine.h index 85517f0dcf..8d91174193 100644 --- a/engine/src/flutter/shell/common/engine.h +++ b/engine/src/flutter/shell/common/engine.h @@ -150,12 +150,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// `CustomAccessibilityActionUpdates`, /// `PlatformView::UpdateSemantics` /// + /// @param[in] view_id The ID of the view that this update is for /// @param[in] updates A map with the stable semantics node identifier as /// key and the node properties as the value. /// @param[in] actions A map with the stable semantics node identifier as /// key and the custom node action as the value. /// virtual void OnEngineUpdateSemantics( + int64_t view_id, SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions) = 0; @@ -819,12 +821,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// originates on the platform view and has been forwarded to the /// engine here on the UI task runner by the shell. /// + /// @param[in] view_id The identifier of the view. /// @param[in] node_id The identifier of the accessibility node. /// @param[in] action The accessibility related action performed on the /// node of the specified ID. /// @param[in] args Optional data that applies to the specified action. /// - void DispatchSemanticsAction(int node_id, + void DispatchSemanticsAction(int64_t view_id, + int node_id, SemanticsAction action, fml::MallocMapping args); @@ -1008,7 +1012,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { float device_pixel_ratio) override; // |RuntimeDelegate| - void UpdateSemantics(SemanticsNodeUpdates update, + void UpdateSemantics(int64_t view_id, + SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) override; // |RuntimeDelegate| diff --git a/engine/src/flutter/shell/common/engine_animator_unittests.cc b/engine/src/flutter/shell/common/engine_animator_unittests.cc index f0d7407ad3..fc97a8c0e2 100644 --- a/engine/src/flutter/shell/common/engine_animator_unittests.cc +++ b/engine/src/flutter/shell/common/engine_animator_unittests.cc @@ -54,7 +54,7 @@ class MockDelegate : public Engine::Delegate { public: MOCK_METHOD(void, OnEngineUpdateSemantics, - (SemanticsNodeUpdates, CustomAccessibilityActionUpdates), + (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); MOCK_METHOD(void, OnEngineHandlePlatformMessage, diff --git a/engine/src/flutter/shell/common/engine_unittests.cc b/engine/src/flutter/shell/common/engine_unittests.cc index 97a589d486..131db5bff6 100644 --- a/engine/src/flutter/shell/common/engine_unittests.cc +++ b/engine/src/flutter/shell/common/engine_unittests.cc @@ -62,7 +62,7 @@ class MockDelegate : public Engine::Delegate { public: MOCK_METHOD(void, OnEngineUpdateSemantics, - (SemanticsNodeUpdates, CustomAccessibilityActionUpdates), + (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); MOCK_METHOD(void, OnEngineHandlePlatformMessage, @@ -113,7 +113,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { (override)); MOCK_METHOD(void, UpdateSemantics, - (SemanticsNodeUpdates, CustomAccessibilityActionUpdates), + (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); MOCK_METHOD(void, HandlePlatformMessage, diff --git a/engine/src/flutter/shell/common/platform_view.cc b/engine/src/flutter/shell/common/platform_view.cc index 414155d37b..9ca2804201 100644 --- a/engine/src/flutter/shell/common/platform_view.cc +++ b/engine/src/flutter/shell/common/platform_view.cc @@ -35,10 +35,11 @@ void PlatformView::DispatchPointerDataPacket( delegate_.OnPlatformViewDispatchPointerDataPacket(std::move(packet)); } -void PlatformView::DispatchSemanticsAction(int32_t node_id, +void PlatformView::DispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) { - delegate_.OnPlatformViewDispatchSemanticsAction(node_id, action, + delegate_.OnPlatformViewDispatchSemanticsAction(view_id, node_id, action, std::move(args)); } @@ -124,6 +125,7 @@ fml::WeakPtr PlatformView::GetWeakPtr() const { } void PlatformView::UpdateSemantics( + int64_t view_id, SemanticsNodeUpdates update, // NOLINT(performance-unnecessary-value-param) // NOLINTNEXTLINE(performance-unnecessary-value-param) CustomAccessibilityActionUpdates actions) {} diff --git a/engine/src/flutter/shell/common/platform_view.h b/engine/src/flutter/shell/common/platform_view.h index 78236428e4..7be2b1ca09 100644 --- a/engine/src/flutter/shell/common/platform_view.h +++ b/engine/src/flutter/shell/common/platform_view.h @@ -188,6 +188,7 @@ class PlatformView { /// event must be forwarded to the running root isolate hosted /// by the engine on the UI thread. /// + /// @param[in] view_id The identifier of the view that contains this node. /// @param[in] node_id The identifier of the accessibility node. /// @param[in] action The accessibility related action performed on the /// node of the specified ID. @@ -195,6 +196,7 @@ class PlatformView { /// specified action. /// virtual void OnPlatformViewDispatchSemanticsAction( + int64_t view_id, int32_t node_id, SemanticsAction action, fml::MallocMapping args) = 0; @@ -450,12 +452,14 @@ class PlatformView { /// @brief Used by embedders to dispatch an accessibility action to a /// running isolate hosted by the engine. /// + /// @param[in] view_id The identifier of the view. /// @param[in] node_id The identifier of the accessibility node on which to /// perform the action. /// @param[in] action The action /// @param[in] args The arguments /// - void DispatchSemanticsAction(int32_t node_id, + void DispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args); @@ -500,12 +504,14 @@ class PlatformView { /// @see SemanticsNode, SemticsNodeUpdates, /// CustomAccessibilityActionUpdates /// + /// @param[in] view_id The ID of the view that this update is for /// @param[in] updates A map with the stable semantics node identifier as /// key and the node properties as the value. /// @param[in] actions A map with the stable semantics node identifier as /// key and the custom node action as the value. /// - virtual void UpdateSemantics(SemanticsNodeUpdates updates, + virtual void UpdateSemantics(int64_t view_id, + SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions); //---------------------------------------------------------------------------- diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index 214483596e..48bcac1d16 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -1107,7 +1107,8 @@ void Shell::OnPlatformViewDispatchPointerDataPacket( } // |PlatformView::Delegate| -void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id, +void Shell::OnPlatformViewDispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) { FML_DCHECK(is_set_up_); @@ -1115,10 +1116,11 @@ void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id, fml::TaskRunner::RunNowAndFlushMessages( task_runners_.GetUITaskRunner(), - fml::MakeCopyable([engine = engine_->GetWeakPtr(), node_id, action, - args = std::move(args)]() mutable { + fml::MakeCopyable([engine = engine_->GetWeakPtr(), view_id, node_id, + action, args = std::move(args)]() mutable { if (engine) { - engine->DispatchSemanticsAction(node_id, action, std::move(args)); + engine->DispatchSemanticsAction(view_id, node_id, action, + std::move(args)); } })); } @@ -1315,7 +1317,8 @@ void Shell::OnAnimatorDrawLastLayerTrees( } // |Engine::Delegate| -void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update, +void Shell::OnEngineUpdateSemantics(int64_t view_id, + SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) { FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); @@ -1323,9 +1326,9 @@ void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update, task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask( task_runners_.GetPlatformTaskRunner(), [view = platform_view_->GetWeakPtr(), update = std::move(update), - actions = std::move(actions)] { + actions = std::move(actions), view_id = view_id] { if (view) { - view->UpdateSemantics(update, actions); + view->UpdateSemantics(view_id, update, actions); } }); } diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h index 971118fbb1..a5305e7abf 100644 --- a/engine/src/flutter/shell/common/shell.h +++ b/engine/src/flutter/shell/common/shell.h @@ -605,7 +605,8 @@ class Shell final : public PlatformView::Delegate, std::unique_ptr packet) override; // |PlatformView::Delegate| - void OnPlatformViewDispatchSemanticsAction(int32_t node_id, + void OnPlatformViewDispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) override; @@ -666,6 +667,7 @@ class Shell final : public PlatformView::Delegate, // |Engine::Delegate| void OnEngineUpdateSemantics( + int64_t view_id, SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) override; diff --git a/engine/src/flutter/shell/common/shell_test.cc b/engine/src/flutter/shell/common/shell_test.cc index 1ee1aecc17..7dc7f80369 100644 --- a/engine/src/flutter/shell/common/shell_test.cc +++ b/engine/src/flutter/shell/common/shell_test.cc @@ -66,10 +66,11 @@ void ShellTest::SendPlatformMessage(Shell* shell, } void ShellTest::SendSemanticsAction(Shell* shell, + int64_t view_id, int32_t node_id, SemanticsAction action, fml::MallocMapping args) { - shell->OnPlatformViewDispatchSemanticsAction(node_id, action, + shell->OnPlatformViewDispatchSemanticsAction(view_id, node_id, action, std::move(args)); } diff --git a/engine/src/flutter/shell/common/shell_test.h b/engine/src/flutter/shell/common/shell_test.h index 90a312c676..0fd729f5a6 100644 --- a/engine/src/flutter/shell/common/shell_test.h +++ b/engine/src/flutter/shell/common/shell_test.h @@ -92,6 +92,7 @@ class ShellTest : public FixtureTest { std::unique_ptr message); void SendSemanticsAction(Shell* shell, + int64_t view_id, int32_t node_id, SemanticsAction action, fml::MallocMapping args); diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 1554d6f619..ea292f6c09 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -143,7 +143,10 @@ class MockPlatformViewDelegate : public PlatformView::Delegate { MOCK_METHOD(void, OnPlatformViewDispatchSemanticsAction, - (int32_t id, SemanticsAction action, fml::MallocMapping args), + (int64_t view_id, + int32_t id, + SemanticsAction action, + fml::MallocMapping args), (override)); MOCK_METHOD(void, @@ -4341,7 +4344,7 @@ TEST_F(ShellTest, SemanticsActionsFlushMessageLoop) { CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); })); task_runners.GetPlatformTaskRunner()->PostTask([&] { - SendSemanticsAction(shell.get(), 0, SemanticsAction::kTap, + SendSemanticsAction(shell.get(), 456, 0, SemanticsAction::kTap, fml::MallocMapping(nullptr, 0)); }); latch.Wait(); diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.cc b/engine/src/flutter/shell/platform/android/platform_view_android.cc index 9114d568de..fbcfdcfb70 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android.cc @@ -44,6 +44,7 @@ namespace flutter { namespace { static constexpr int kMinAPILevelHCPP = 34; +static constexpr int64_t kImplicitViewId = 0; AndroidContext::ContextSettings CreateContextSettings( const Settings& p_settings) { @@ -265,13 +266,15 @@ void PlatformViewAndroid::OnPreEngineRestart() const { } void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env, - jint id, + jint node_id, jint action, jobject args, jint args_position) { + // TODO(team-android): Remove implicit view assumption. + // https://github.com/flutter/flutter/issues/142845 if (env->IsSameObject(args, NULL)) { PlatformView::DispatchSemanticsAction( - id, static_cast(action), + kImplicitViewId, node_id, static_cast(action), fml::MallocMapping()); return; } @@ -280,12 +283,13 @@ void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env, auto args_vector = fml::MallocMapping::Copy(args_data, args_position); PlatformView::DispatchSemanticsAction( - id, static_cast(action), + kImplicitViewId, node_id, static_cast(action), std::move(args_vector)); } // |PlatformView| void PlatformViewAndroid::UpdateSemantics( + int64_t view_id, flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { platform_view_android_delegate_.UpdateSemantics(update, actions); diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.h b/engine/src/flutter/shell/platform/android/platform_view_android.h index fcf2fa9ead..726159d492 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.h +++ b/engine/src/flutter/shell/platform/android/platform_view_android.h @@ -133,6 +133,7 @@ class PlatformViewAndroid final : public PlatformView { // |PlatformView| void UpdateSemantics( + int64_t view_id, flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) override; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index 5d5fc3456f..7f885321ea 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -35,7 +35,8 @@ class FakeDelegate : public PlatformView::Delegate { void OnPlatformViewDispatchPlatformMessage(std::unique_ptr message) override {} void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr packet) override { } - void OnPlatformViewDispatchSemanticsAction(int32_t id, + void OnPlatformViewDispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) override {} void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index b9d23f9f9a..7b8d2a43c7 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -270,7 +270,8 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De void OnPlatformViewDispatchPlatformMessage(std::unique_ptr message) override {} void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr packet) override { } - void OnPlatformViewDispatchSemanticsAction(int32_t id, + void OnPlatformViewDispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) override {} void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index daf6389834..ca7feb93fd 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -12,6 +12,8 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" +#include "flutter/common/constants.h" + #pragma GCC diagnostic error "-Wundeclared-selector" FLUTTER_ASSERT_ARC @@ -235,14 +237,20 @@ void AccessibilityBridge::UpdateSemantics( } } -void AccessibilityBridge::DispatchSemanticsAction(int32_t uid, flutter::SemanticsAction action) { - platform_view_->DispatchSemanticsAction(uid, action, {}); +void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid, + flutter::SemanticsAction action) { + // TODO(team-ios): Remove implicit view assumption. + // https://github.com/flutter/flutter/issues/142845 + platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid, action, {}); } -void AccessibilityBridge::DispatchSemanticsAction(int32_t uid, +void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid, flutter::SemanticsAction action, fml::MallocMapping args) { - platform_view_->DispatchSemanticsAction(uid, action, std::move(args)); + // TODO(team-ios): Remove implicit view assumption. + // https://github.com/flutter/flutter/issues/142845 + platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid, action, + std::move(args)); } static void ReplaceSemanticsObject(SemanticsObject* oldObject, diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 0f6ce68926..8f372f579f 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -82,7 +82,8 @@ class MockDelegate : public PlatformView::Delegate { void OnPlatformViewDispatchPlatformMessage(std::unique_ptr message) override {} void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr packet) override { } - void OnPlatformViewDispatchSemanticsAction(int32_t id, + void OnPlatformViewDispatchSemanticsAction(int64_t view_id, + int32_t node_id, SemanticsAction action, fml::MallocMapping args) override {} void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h index 1013aca65e..274dff306c 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h +++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h @@ -103,7 +103,8 @@ class PlatformViewIOS final : public PlatformView { void SetAccessibilityFeatures(int32_t flags) override; // |PlatformView| - void UpdateSemantics(flutter::SemanticsNodeUpdates update, + void UpdateSemantics(int64_t view_id, + flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) override; // |PlatformView| diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm index 3e12bd1f45..b9b33e86f7 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm @@ -181,7 +181,8 @@ void PlatformViewIOS::SetAccessibilityFeatures(int32_t flags) { } // |PlatformView| -void PlatformViewIOS::UpdateSemantics(flutter::SemanticsNodeUpdates update, +void PlatformViewIOS::UpdateSemantics(int64_t view_id, + flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { FML_DCHECK(owner_controller_); if (accessibility_bridge_) { diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm index f79f60b10e..b47b8be4d1 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm @@ -332,6 +332,8 @@ AccessibilityBridgeMac::MacOSEventsFromAXEvent(ui::AXEventGenerator::Event event void AccessibilityBridgeMac::DispatchAccessibilityAction(ui::AXNode::AXID target, FlutterSemanticsAction action, fml::MallocMapping data) { + // TODO(mattkae): Remove implicit view assumption. + // https://github.com/flutter/flutter/issues/142845 NSCAssert(flutter_engine_, @"Flutter engine should not be deallocated"); NSCAssert(view_controller_.viewLoaded && view_controller_.view.window, @"The accessibility bridge should not receive accessibility actions if the flutter view" diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc index 2c55865c79..e5f7c4353e 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder.cc @@ -1843,9 +1843,9 @@ CreateEmbedderSemanticsUpdateCallbackV1( update_semantics_custom_action_callback, void* user_data) { return [update_semantics_node_callback, - update_semantics_custom_action_callback, - user_data](const flutter::SemanticsNodeUpdates& nodes, - const flutter::CustomAccessibilityActionUpdates& actions) { + update_semantics_custom_action_callback, user_data]( + int64_t view_id, const flutter::SemanticsNodeUpdates& nodes, + const flutter::CustomAccessibilityActionUpdates& actions) { flutter::EmbedderSemanticsUpdate update{nodes, actions}; FlutterSemanticsUpdate* update_ptr = update.get(); @@ -1890,7 +1890,7 @@ CreateEmbedderSemanticsUpdateCallbackV2( FlutterUpdateSemanticsCallback update_semantics_callback, void* user_data) { return [update_semantics_callback, user_data]( - const flutter::SemanticsNodeUpdates& nodes, + int64_t view_id, const flutter::SemanticsNodeUpdates& nodes, const flutter::CustomAccessibilityActionUpdates& actions) { flutter::EmbedderSemanticsUpdate update{nodes, actions}; @@ -1905,9 +1905,9 @@ CreateEmbedderSemanticsUpdateCallbackV3( FlutterUpdateSemanticsCallback2 update_semantics_callback, void* user_data) { return [update_semantics_callback, user_data]( - const flutter::SemanticsNodeUpdates& nodes, + int64_t view_id, const flutter::SemanticsNodeUpdates& nodes, const flutter::CustomAccessibilityActionUpdates& actions) { - flutter::EmbedderSemanticsUpdate2 update{nodes, actions}; + flutter::EmbedderSemanticsUpdate2 update{view_id, nodes, actions}; update_semantics_callback(update.get(), user_data); }; @@ -3189,14 +3189,27 @@ FlutterEngineResult FlutterEngineDispatchSemanticsAction( FlutterSemanticsAction action, const uint8_t* data, size_t data_length) { + FlutterSendSemanticsActionInfo info{ + .struct_size = sizeof(FlutterSendSemanticsActionInfo), + .view_id = kFlutterImplicitViewId, + .node_id = node_id, + .action = action, + .data = data, + .data_length = data_length}; + return FlutterEngineSendSemanticsAction(engine, &info); +} + +FlutterEngineResult FlutterEngineSendSemanticsAction( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterSendSemanticsActionInfo* info) { if (engine == nullptr) { return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); } - auto engine_action = static_cast(action); + auto engine_action = static_cast(info->action); if (!reinterpret_cast(engine) ->DispatchSemanticsAction( - node_id, engine_action, - fml::MallocMapping::Copy(data, data_length))) { + info->view_id, info->node_id, engine_action, + fml::MallocMapping::Copy(info->data, info->data_length))) { return LOG_EMBEDDER_ERROR(kInternalInconsistency, "Could not dispatch semantics action."); } @@ -3705,6 +3718,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses( SET_PROC(UpdateAccessibilityFeatures, FlutterEngineUpdateAccessibilityFeatures); SET_PROC(DispatchSemanticsAction, FlutterEngineDispatchSemanticsAction); + SET_PROC(SendSemanticsAction, FlutterEngineSendSemanticsAction); SET_PROC(OnVsync, FlutterEngineOnVsync); SET_PROC(ReloadSystemFonts, FlutterEngineReloadSystemFonts); SET_PROC(TraceEventDurationBegin, FlutterEngineTraceEventDurationBegin); diff --git a/engine/src/flutter/shell/platform/embedder/embedder.h b/engine/src/flutter/shell/platform/embedder/embedder.h index 5a85bee325..07f48914d2 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.h +++ b/engine/src/flutter/shell/platform/embedder/embedder.h @@ -1687,6 +1687,8 @@ typedef struct { /// Array of semantics custom action pointers. Has length /// `custom_action_count`. FlutterSemanticsCustomAction2** custom_actions; + // The ID of the view that this update is associated with. + FlutterViewId view_id; } FlutterSemanticsUpdate2; typedef void (*FlutterUpdateSemanticsNodeCallback)( @@ -2645,6 +2647,27 @@ typedef struct { int64_t engine_id; } FlutterProjectArgs; +typedef struct { + /// The size of this struct. Must be + /// sizeof(FlutterSendSemanticsActionInfo). + size_t struct_size; + + /// The ID of the view that includes the node. + FlutterViewId view_id; + + /// The semantics node identifier. + uint64_t node_id; + + /// The semantics action. + FlutterSemanticsAction action; + + /// Data associated with the action. + const uint8_t* data; + + /// The data length. + size_t data_length; +} FlutterSendSemanticsActionInfo; + #ifndef FLUTTER_ENGINE_NO_PROTOTYPES // NOLINTBEGIN(google-objc-function-naming) @@ -3078,7 +3101,10 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( FlutterAccessibilityFeature features); //------------------------------------------------------------------------------ -/// @brief Dispatch a semantics action to the specified semantics node. +/// @brief Dispatch a semantics action to the specified semantics node +/// in the implicit view. +/// +/// @deprecated Use `FlutterEngineSendSemanticsAction` instead. /// /// @param[in] engine A running engine instance. /// @param[in] node_id The semantics node identifier. @@ -3096,6 +3122,22 @@ FlutterEngineResult FlutterEngineDispatchSemanticsAction( const uint8_t* data, size_t data_length); +//------------------------------------------------------------------------------ +/// @brief Dispatch a semantics action to the specified semantics node +/// within a specific view. +/// +/// @param[in] engine A running engine instance. +/// @param[in] info The dispatch semantics on view arguments. +/// This can be deallocated once +/// |FlutterEngineSendSemanticsAction| returns. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSendSemanticsAction( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterSendSemanticsActionInfo* info); + //------------------------------------------------------------------------------ /// @brief Notify the engine that a vsync event occurred. A baton passed to /// the platform via the vsync callback must be returned. This call @@ -3483,6 +3525,9 @@ typedef FlutterEngineResult (*FlutterEngineDispatchSemanticsActionFnPtr)( FlutterSemanticsAction action, const uint8_t* data, size_t data_length); +typedef FlutterEngineResult (*FlutterEngineSendSemanticsActionFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterSendSemanticsActionInfo* info); typedef FlutterEngineResult (*FlutterEngineOnVsyncFnPtr)( FLUTTER_API_SYMBOL(FlutterEngine) engine, intptr_t baton, @@ -3585,6 +3630,7 @@ typedef struct { FlutterEngineAddViewFnPtr AddView; FlutterEngineRemoveViewFnPtr RemoveView; FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent; + FlutterEngineSendSemanticsActionFnPtr SendSemanticsAction; } FlutterEngineProcTable; //------------------------------------------------------------------------------ diff --git a/engine/src/flutter/shell/platform/embedder/embedder_engine.cc b/engine/src/flutter/shell/platform/embedder/embedder_engine.cc index ff764224b8..d3e78ee553 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_engine.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_engine.cc @@ -235,7 +235,8 @@ bool EmbedderEngine::SetAccessibilityFeatures(int32_t flags) { return true; } -bool EmbedderEngine::DispatchSemanticsAction(int node_id, +bool EmbedderEngine::DispatchSemanticsAction(int64_t view_id, + int node_id, flutter::SemanticsAction action, fml::MallocMapping args) { if (!IsValid()) { @@ -245,7 +246,8 @@ bool EmbedderEngine::DispatchSemanticsAction(int node_id, if (!platform_view) { return false; } - platform_view->DispatchSemanticsAction(node_id, action, std::move(args)); + platform_view->DispatchSemanticsAction(view_id, node_id, action, + std::move(args)); return true; } diff --git a/engine/src/flutter/shell/platform/embedder/embedder_engine.h b/engine/src/flutter/shell/platform/embedder/embedder_engine.h index a885237ed1..c9c16ecb9e 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_engine.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_engine.h @@ -68,7 +68,8 @@ class EmbedderEngine { bool SetAccessibilityFeatures(int32_t flags); - bool DispatchSemanticsAction(int node_id, + bool DispatchSemanticsAction(int64_t view_id, + int node_id, flutter::SemanticsAction action, fml::MallocMapping args); diff --git a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc index 1670d7c734..330c6e63d4 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc @@ -88,6 +88,7 @@ void EmbedderSemanticsUpdate::AddAction( EmbedderSemanticsUpdate::~EmbedderSemanticsUpdate() {} EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2( + int64_t view_id, const SemanticsNodeUpdates& nodes, const CustomAccessibilityActionUpdates& actions) { nodes_.reserve(nodes.size()); @@ -111,13 +112,12 @@ EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2( action_pointers_.push_back(&actions_[i]); } - update_ = { - .struct_size = sizeof(FlutterSemanticsUpdate2), - .node_count = node_pointers_.size(), - .nodes = node_pointers_.data(), - .custom_action_count = action_pointers_.size(), - .custom_actions = action_pointers_.data(), - }; + update_ = {.struct_size = sizeof(FlutterSemanticsUpdate2), + .node_count = node_pointers_.size(), + .nodes = node_pointers_.data(), + .custom_action_count = action_pointers_.size(), + .custom_actions = action_pointers_.data(), + .view_id = view_id}; } EmbedderSemanticsUpdate2::~EmbedderSemanticsUpdate2() {} diff --git a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h index 1e628e41ad..3dbc5a5b64 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h @@ -47,7 +47,8 @@ class EmbedderSemanticsUpdate { // and the temporary embedder-specific objects are automatically cleaned up. class EmbedderSemanticsUpdate2 { public: - EmbedderSemanticsUpdate2(const SemanticsNodeUpdates& nodes, + EmbedderSemanticsUpdate2(int64_t view_id, + const SemanticsNodeUpdates& nodes, const CustomAccessibilityActionUpdates& actions); ~EmbedderSemanticsUpdate2(); diff --git a/engine/src/flutter/shell/platform/embedder/fixtures/main.dart b/engine/src/flutter/shell/platform/embedder/fixtures/main.dart index 4cf5b8f1bf..34eb8ff7b8 100644 --- a/engine/src/flutter/shell/platform/embedder/fixtures/main.dart +++ b/engine/src/flutter/shell/platform/embedder/fixtures/main.dart @@ -1632,3 +1632,70 @@ void testSendViewFocusChangeRequest() { direction: ViewFocusDirection.backward, ); } + +@pragma('vm:entry-point') +// ignore: non_constant_identifier_names +Future a11y_main_multi_view() async { + // 1: Return initial state (semantics disabled). + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); + + // 2: Add the first view (implicitly handled by PlatformDispatcher). + // 3: Add the second view (implicitly handled by PlatformDispatcher). + + // 4: Await semantics enabled from embedder. + await semanticsChanged; + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); + + // 5: Return initial state of accessibility features. + notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion); + + // 6: Fire semantics updates. + SemanticsUpdateBuilder createForView(FlutterView view) { + return SemanticsUpdateBuilder()..updateNode( + id: view.viewId + 1, // For simplicity, give each node an id of viewId + 1 + identifier: '', + label: 'A: root', + labelAttributes: [], + rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), + transform: kTestTransform, + childrenInTraversalOrder: Int32List.fromList([84, 96]), + childrenInHitTestOrder: Int32List.fromList([96, 84]), + actions: 0, + flags: 0, + maxValueLength: 0, + currentValueLength: 0, + textSelectionBase: 0, + textSelectionExtent: 0, + platformViewId: 0, + scrollChildren: 0, + scrollIndex: 0, + scrollPosition: 0.0, + scrollExtentMax: 0.0, + scrollExtentMin: 0.0, + elevation: 0.0, + thickness: 0.0, + hint: '', + hintAttributes: [], + value: '', + valueAttributes: [], + increasedValue: '', + increasedValueAttributes: [], + decreasedValue: '', + decreasedValueAttributes: [], + tooltip: 'tooltip', + textDirection: TextDirection.ltr, + additionalActions: Int32List(0), + controlsNodes: null, + ); + } + + for (final view in PlatformDispatcher.instance.views) { + view.updateSemantics(createForView(view).build()); + } + + signalNativeTest(); + + // 7: Await semantics disabled from embedder. + await semanticsChanged; + notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled); +} diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc index 950d3ee58b..712a5287e3 100644 --- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc @@ -113,11 +113,12 @@ PlatformViewEmbedder::PlatformViewEmbedder( PlatformViewEmbedder::~PlatformViewEmbedder() = default; void PlatformViewEmbedder::UpdateSemantics( + int64_t view_id, flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { if (platform_dispatch_table_.update_semantics_callback != nullptr) { - platform_dispatch_table_.update_semantics_callback(std::move(update), - std::move(actions)); + platform_dispatch_table_.update_semantics_callback( + view_id, std::move(update), std::move(actions)); } } diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h index 2c4ab691f3..1b9f500140 100644 --- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h +++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h @@ -36,7 +36,8 @@ namespace flutter { class PlatformViewEmbedder final : public PlatformView { public: using UpdateSemanticsCallback = - std::function; using PlatformMessageResponseCallback = std::function)>; @@ -104,6 +105,7 @@ class PlatformViewEmbedder final : public PlatformView { // |PlatformView| void UpdateSemantics( + int64_t view_id, flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) override; diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc b/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc index 9963a2397b..129627af45 100644 --- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc +++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc @@ -54,7 +54,10 @@ class MockDelegate : public PlatformView::Delegate { (override)); MOCK_METHOD(void, OnPlatformViewDispatchSemanticsAction, - (int32_t id, SemanticsAction action, fml::MallocMapping args), + (int64_t view_id, + int32_t node_id, + SemanticsAction action, + fml::MallocMapping args), (override)); MOCK_METHOD(void, OnPlatformViewSetSemanticsEnabled, diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_a11y_unittests.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_a11y_unittests.cc index c6ad07a02b..091e3dc49a 100644 --- a/engine/src/flutter/shell/platform/embedder/tests/embedder_a11y_unittests.cc +++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_a11y_unittests.cc @@ -774,6 +774,184 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV1Callbacks) { #endif } +TEST_F(EmbedderA11yTest, A11yTreesAreConsistentWithMultipleViews) { +#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG) + GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia."; +#else + auto& context = GetEmbedderContext(); + + fml::AutoResetWaitableEvent signal_native_latch; + + // Called by the Dart text fixture on the UI thread to signal that the C++ + // unittest should resume. + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY(([&signal_native_latch](Dart_NativeArguments) { + signal_native_latch.Signal(); + }))); + + // Called by test fixture on UI thread to pass data back to this test. + NativeEntry notify_semantics_enabled_callback; + context.AddNativeCallback( + "NotifySemanticsEnabled", + CREATE_NATIVE_ENTRY( + ([¬ify_semantics_enabled_callback](Dart_NativeArguments args) { + ASSERT_NE(notify_semantics_enabled_callback, nullptr); + notify_semantics_enabled_callback(args); + }))); + + NativeEntry notify_accessibility_features_callback; + context.AddNativeCallback( + "NotifyAccessibilityFeatures", + CREATE_NATIVE_ENTRY(( + [¬ify_accessibility_features_callback](Dart_NativeArguments args) { + ASSERT_NE(notify_accessibility_features_callback, nullptr); + notify_accessibility_features_callback(args); + }))); + + int num_times_set_semantics_update_callback2_called = 0; + fml::AutoResetWaitableEvent semantics_update_latch; + context.SetSemanticsUpdateCallback2( + [&](const FlutterSemanticsUpdate2* update) { + num_times_set_semantics_update_callback2_called++; + ASSERT_EQ(size_t(1), update->node_count); + + for (size_t i = 0; i < update->node_count; i++) { + const FlutterSemanticsNode2* node = update->nodes[i]; + + // The node ID should be the view_id + 1 + ASSERT_EQ(node->id, update->view_id + 1); + ASSERT_EQ(1.0, node->transform.scaleX); + ASSERT_EQ(2.0, node->transform.skewX); + ASSERT_EQ(3.0, node->transform.transX); + ASSERT_EQ(4.0, node->transform.skewY); + ASSERT_EQ(5.0, node->transform.scaleY); + ASSERT_EQ(6.0, node->transform.transY); + ASSERT_EQ(7.0, node->transform.pers0); + ASSERT_EQ(8.0, node->transform.pers1); + ASSERT_EQ(9.0, node->transform.pers2); + ASSERT_EQ(std::strncmp(kTooltip, node->tooltip, sizeof(kTooltip) - 1), + 0); + } + + if (num_times_set_semantics_update_callback2_called == 3) { + semantics_update_latch.Signal(); + } + }); + + EmbedderConfigBuilder builder(context); + builder.SetSurface(SkISize::Make(1, 1)); + builder.SetDartEntrypoint("a11y_main_multi_view"); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // 1: Wait for initial notifySemanticsEnabled(false). + fml::AutoResetWaitableEvent notify_semantics_enabled_latch; + notify_semantics_enabled_callback = [&](Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + bool enabled = + ::tonic::DartConverter::FromArguments(args, 0, exception); + ASSERT_FALSE(enabled); + notify_semantics_enabled_latch.Signal(); + }; + notify_semantics_enabled_latch.Wait(); + + const int64_t first_view_id = 1; + const int64_t second_view_id = 2; + + // 2. Add the first view and wait for the add view callback. + FlutterWindowMetricsEvent window_metrics_event; + window_metrics_event.struct_size = sizeof(FlutterWindowMetricsEvent); + window_metrics_event.width = 100; + window_metrics_event.height = 100; + window_metrics_event.pixel_ratio = 1.0; + window_metrics_event.left = 0; + window_metrics_event.top = 0; + window_metrics_event.physical_view_inset_top = 0.0; + window_metrics_event.physical_view_inset_right = 0.0; + window_metrics_event.physical_view_inset_bottom = 0.0; + window_metrics_event.physical_view_inset_left = 0.0; + window_metrics_event.display_id = 0; + window_metrics_event.view_id = first_view_id; + + FlutterAddViewInfo add_view_info; + add_view_info.struct_size = sizeof(FlutterAddViewInfo); + add_view_info.view_id = first_view_id; + add_view_info.view_metrics = &window_metrics_event; + fml::AutoResetWaitableEvent notify_add_view_latch; + add_view_info.user_data = ¬ify_add_view_latch; + add_view_info.add_view_callback = [](const FlutterAddViewResult* result) { + EXPECT_TRUE(result->added); + auto latch = + reinterpret_cast(result->user_data); + latch->Signal(); + }; + FlutterEngineAddView(engine.get(), &add_view_info); + notify_add_view_latch.Wait(); + + // 3. Add the second view and wait for the add view callback. + add_view_info.view_id = second_view_id; + window_metrics_event.view_id = second_view_id; + add_view_info.add_view_callback = [](const FlutterAddViewResult* result) { + EXPECT_TRUE(result->added); + auto latch = + reinterpret_cast(result->user_data); + latch->Signal(); + }; + FlutterEngineAddView(engine.get(), &add_view_info); + notify_add_view_latch.Wait(); + + // Prepare notifyAccessibilityFeatures callback. + fml::AutoResetWaitableEvent notify_features_latch; + notify_accessibility_features_callback = [&](Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + bool enabled = + ::tonic::DartConverter::FromArguments(args, 0, exception); + ASSERT_FALSE(enabled); + notify_features_latch.Signal(); + }; + + // 4: Enable semantics. Wait for notifySemanticsEnabled(true). + fml::AutoResetWaitableEvent notify_semantics_enabled_latch_2; + notify_semantics_enabled_callback = [&](Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + bool enabled = + ::tonic::DartConverter::FromArguments(args, 0, exception); + ASSERT_TRUE(enabled); + notify_semantics_enabled_latch_2.Signal(); + }; + auto result = FlutterEngineUpdateSemanticsEnabled(engine.get(), true); + ASSERT_EQ(result, FlutterEngineResult::kSuccess); + notify_semantics_enabled_latch_2.Wait(); + + // 5: Wait for notifyAccessibilityFeatures (reduce_motion == false) + notify_features_latch.Wait(); + + // 6: Wait for UpdateSemantics callback on platform (current) thread. + // for all pending updates. Expect that it is called 3 times (once for + // the implicit view and two more times for the views that were manually + // added). + signal_native_latch.Wait(); + fml::MessageLoop::GetCurrent().RunExpiredTasksNow(); + semantics_update_latch.Wait(); + EXPECT_EQ(num_times_set_semantics_update_callback2_called, 3); + + // 7: Disable semantics. Wait for NotifySemanticsEnabled(false). + fml::AutoResetWaitableEvent notify_semantics_enabled_latch_3; + notify_semantics_enabled_callback = [&](Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + bool enabled = + ::tonic::DartConverter::FromArguments(args, 0, exception); + ASSERT_FALSE(enabled); + notify_semantics_enabled_latch_3.Signal(); + }; + result = FlutterEngineUpdateSemanticsEnabled(engine.get(), false); + ASSERT_EQ(result, FlutterEngineResult::kSuccess); + notify_semantics_enabled_latch_3.Wait(); +#endif +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/engine.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/engine.cc index 760ea133b2..17678f222b 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/engine.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/engine.cc @@ -42,6 +42,7 @@ namespace flutter_runner { namespace { +constexpr static int64_t kImplicitViewId = 0; zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) { zx_handle_t handle = view_ref.reference.get(); @@ -393,7 +394,10 @@ void Engine::Initialize( auto platform_view = shell_->GetPlatformView(); if (platform_view) { - platform_view->DispatchSemanticsAction(node_id, action, {}); + // TODO(fuchsia): Remove implicit view assumption. + // https://github.com/flutter/flutter/issues/142845 + platform_view->DispatchSemanticsAction(kImplicitViewId, node_id, + action, {}); } }; diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.cc index 8e78c4ab94..35147128ee 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.cc @@ -676,6 +676,7 @@ void PlatformView::SetSemanticsEnabled(bool enabled) { // |flutter::PlatformView| void PlatformView::UpdateSemantics( + int64_t view_id, flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { const float pixel_ratio = diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.h b/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.h index 8e6ffd0ec0..b9314728f4 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.h +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/platform_view.h @@ -134,6 +134,7 @@ class PlatformView : public flutter::PlatformView { // |flutter::PlatformView| void UpdateSemantics( + int64_t view_id, flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) override; diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc index aac3bf5591..fc010ed923 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc @@ -121,7 +121,8 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { std::unique_ptr packet, std::function callback) {} // |flutter::PlatformView::Delegate| - void OnPlatformViewDispatchSemanticsAction(int32_t id, + void OnPlatformViewDispatchSemanticsAction(int64_t view_id, + int32_t node_id, flutter::SemanticsAction action, fml::MallocMapping args) {} // |flutter::PlatformView::Delegate| diff --git a/engine/src/flutter/shell/platform/linux/fl_accessible_node.cc b/engine/src/flutter/shell/platform/linux/fl_accessible_node.cc index 37d54833fd..89de81a91c 100644 --- a/engine/src/flutter/shell/platform/linux/fl_accessible_node.cc +++ b/engine/src/flutter/shell/platform/linux/fl_accessible_node.cc @@ -68,10 +68,13 @@ struct FlAccessibleNodePrivate { // Weak reference to the engine this node is created for. FlEngine* engine; + /// The unique identifier of the view to which this node belongs. + FlutterViewId view_id; + // Weak reference to the parent node of this one or %NULL. AtkObject* parent; - int32_t id; + int32_t node_id; gchar* name; gint index; gint x, y, width, height; @@ -81,7 +84,7 @@ struct FlAccessibleNodePrivate { FlutterSemanticsFlag flags; }; -enum { PROP_0, PROP_ENGINE, PROP_ID, PROP_LAST }; +enum { PROP_0, PROP_ENGINE, PROP_VIEW_ID, PROP_ID, PROP_LAST }; #define FL_ACCESSIBLE_NODE_GET_PRIVATE(node) \ ((FlAccessibleNodePrivate*)fl_accessible_node_get_instance_private( \ @@ -151,8 +154,11 @@ static void fl_accessible_node_set_property(GObject* object, g_object_add_weak_pointer(object, reinterpret_cast(&priv->engine)); break; + case PROP_VIEW_ID: + priv->view_id = g_value_get_int64(value); + break; case PROP_ID: - priv->id = g_value_get_int(value); + priv->node_id = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); @@ -417,7 +423,8 @@ static void fl_accessible_node_perform_action_impl( FlutterSemanticsAction action, GBytes* data) { FlAccessibleNodePrivate* priv = FL_ACCESSIBLE_NODE_GET_PRIVATE(self); - fl_engine_dispatch_semantics_action(priv->engine, priv->id, action, data); + fl_engine_dispatch_semantics_action(priv->engine, priv->view_id, + priv->node_id, action, data); } static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) { @@ -453,10 +460,16 @@ static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) { "engine", "engine", "Flutter engine", fl_engine_get_type(), static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property( + G_OBJECT_CLASS(klass), PROP_VIEW_ID, + g_param_spec_int64( + "view-id", "view-id", "View ID that this node belongs to", 0, + G_MAXINT64, 0, + static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY))); g_object_class_install_property( G_OBJECT_CLASS(klass), PROP_ID, g_param_spec_int( - "id", "id", "Accessibility node ID", 0, G_MAXINT, 0, + "node-id", "node-id", "Accessibility node ID", 0, G_MAXINT, 0, static_cast(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); } @@ -479,9 +492,12 @@ static void fl_accessible_node_init(FlAccessibleNode* self) { priv->children = g_ptr_array_new_with_free_func(g_object_unref); } -FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, int32_t id) { - FlAccessibleNode* self = FL_ACCESSIBLE_NODE(g_object_new( - fl_accessible_node_get_type(), "engine", engine, "id", id, nullptr)); +FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, + FlutterViewId view_id, + int32_t node_id) { + FlAccessibleNode* self = FL_ACCESSIBLE_NODE( + g_object_new(fl_accessible_node_get_type(), "engine", engine, "view-id", + view_id, "node-id", node_id, nullptr)); return self; } diff --git a/engine/src/flutter/shell/platform/linux/fl_accessible_node.h b/engine/src/flutter/shell/platform/linux/fl_accessible_node.h index f793fb68e9..9021abee3a 100644 --- a/engine/src/flutter/shell/platform/linux/fl_accessible_node.h +++ b/engine/src/flutter/shell/platform/linux/fl_accessible_node.h @@ -55,14 +55,17 @@ struct _FlAccessibleNodeClass { /** * fl_accessible_node_new: * @engine: the #FlEngine this node came from. - * @id: the semantics node ID this object represents. + * @view_id: the view ID this object represents. + * @node_id: the semantics node ID this object represents. * * Creates a new accessibility object that exposes Flutter accessibility * information to ATK. * * Returns: a new #FlAccessibleNode. */ -FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, int32_t id); +FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, + FlutterViewId view_id, + int32_t node_id); /** * fl_accessible_node_set_parent: diff --git a/engine/src/flutter/shell/platform/linux/fl_accessible_node_test.cc b/engine/src/flutter/shell/platform/linux/fl_accessible_node_test.cc index 2b59f40f0d..d87427060c 100644 --- a/engine/src/flutter/shell/platform/linux/fl_accessible_node_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_accessible_node_test.cc @@ -12,10 +12,13 @@ TEST(FlAccessibleNodeTest, BuildTree) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) root = fl_accessible_node_new(engine, 0); - g_autoptr(FlAccessibleNode) child1 = fl_accessible_node_new(engine, 1); + int64_t view_id = 123; + g_autoptr(FlAccessibleNode) root = fl_accessible_node_new(engine, view_id, 0); + g_autoptr(FlAccessibleNode) child1 = + fl_accessible_node_new(engine, view_id, 1); fl_accessible_node_set_parent(child1, ATK_OBJECT(root), 0); - g_autoptr(FlAccessibleNode) child2 = fl_accessible_node_new(engine, 1); + g_autoptr(FlAccessibleNode) child2 = + fl_accessible_node_new(engine, view_id, 1); fl_accessible_node_set_parent(child2, ATK_OBJECT(root), 1); g_autoptr(GPtrArray) children = g_ptr_array_new_with_free_func(g_object_unref); @@ -47,7 +50,7 @@ TEST(FlAccessibleNodeTest, SetName) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0); + g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0); fl_accessible_node_set_name(node, "test"); EXPECT_STREQ(atk_object_get_name(ATK_OBJECT(node)), "test"); } @@ -57,7 +60,7 @@ TEST(FlAccessibleNodeTest, SetExtents) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0); + g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0); fl_accessible_node_set_extents(node, 1, 2, 3, 4); gint x, y, width, height; atk_component_get_extents(ATK_COMPONENT(node), &x, &y, &width, &height, @@ -73,7 +76,7 @@ TEST(FlAccessibleNodeTest, SetFlags) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0); + g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0); fl_accessible_node_set_flags( node, static_cast(kFlutterSemanticsFlagIsEnabled | kFlutterSemanticsFlagIsFocusable | @@ -93,7 +96,7 @@ TEST(FlAccessibleNodeTest, GetRole) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0); + g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0); fl_accessible_node_set_flags( node, static_cast(kFlutterSemanticsFlagIsButton)); @@ -127,7 +130,7 @@ TEST(FlAccessibleNodeTest, SetActions) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0); + g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0); fl_accessible_node_set_actions( node, static_cast( kFlutterSemanticsActionTap | kFlutterSemanticsActionLongPress)); diff --git a/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.cc b/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.cc index 8fdfda6afa..17d0a0d763 100644 --- a/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.cc +++ b/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.cc @@ -615,7 +615,10 @@ static void fl_accessible_text_field_init(FlAccessibleTextField* self) { self, G_CONNECT_SWAPPED); } -FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, int32_t id) { +FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, + FlutterViewId view_id, + int32_t id) { return FL_ACCESSIBLE_NODE(g_object_new(fl_accessible_text_field_get_type(), - "engine", engine, "id", id, nullptr)); + "engine", engine, "view-id", view_id, + "node-id", id, nullptr)); } diff --git a/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.h b/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.h index c3346abda1..cee5839d17 100644 --- a/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.h +++ b/engine/src/flutter/shell/platform/linux/fl_accessible_text_field.h @@ -20,6 +20,7 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField, /** * fl_accessible_text_field_new: * @engine: the #FlEngine this node came from. + * @view_id: the ID of the view that contains this semantics node. * @id: the semantics node ID this object represents. * * Creates a new accessibility object that exposes an editable Flutter text @@ -27,7 +28,9 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField, * * Returns: a new #FlAccessibleNode. */ -FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, int32_t id); +FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, + FlutterViewId view_id, + int32_t id); G_END_DECLS diff --git a/engine/src/flutter/shell/platform/linux/fl_accessible_text_field_test.cc b/engine/src/flutter/shell/platform/linux/fl_accessible_text_field_test.cc index a13eddd2e0..90be661eb8 100644 --- a/engine/src/flutter/shell/platform/linux/fl_accessible_text_field_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_accessible_text_field_test.cc @@ -26,7 +26,8 @@ static FlValue* decode_semantic_data(const uint8_t* data, size_t data_length) { TEST(FlAccessibleTextFieldTest, SetValue) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); // "" -> "Flutter" { @@ -83,7 +84,8 @@ TEST(FlAccessibleTextFieldTest, SetValue) { TEST(FlAccessibleTextFieldTest, SetTextSelection) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); // [-1,-1] -> [2,3] { @@ -151,18 +153,17 @@ TEST(FlAccessibleTextFieldTest, PerformAction) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&action_datas](auto engine, uint64_t id, - FlutterSemanticsAction action, const uint8_t* data, - size_t data_length) { - g_ptr_array_add(action_datas, - decode_semantic_data(data, data_length)); - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&action_datas](auto engine, + const FlutterSendSemanticsActionInfo* info) { + g_ptr_array_add(action_datas, + decode_semantic_data(info->data, info->data_length)); + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); fl_accessible_node_set_actions( node, static_cast( kFlutterSemanticsActionMoveCursorForwardByCharacter | @@ -185,7 +186,8 @@ TEST(FlAccessibleTextFieldTest, PerformAction) { TEST(FlAccessibleTextFieldTest, GetCharacterCount) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); EXPECT_EQ(atk_text_get_character_count(ATK_TEXT(node)), 0); @@ -198,7 +200,8 @@ TEST(FlAccessibleTextFieldTest, GetCharacterCount) { TEST(FlAccessibleTextFieldTest, GetText) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); g_autofree gchar* empty = atk_text_get_text(ATK_TEXT(node), 0, -1); EXPECT_STREQ(empty, ""); @@ -219,7 +222,8 @@ TEST(FlAccessibleTextFieldTest, GetText) { TEST(FlAccessibleTextFieldTest, GetCaretOffset) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); EXPECT_EQ(atk_text_get_caret_offset(ATK_TEXT(node)), -1); @@ -240,21 +244,21 @@ TEST(FlAccessibleTextFieldTest, SetCaretOffset) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&base, &extent](auto engine, uint64_t id, - FlutterSemanticsAction action, const uint8_t* data, - size_t data_length) { - EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); - g_autoptr(FlValue) value = decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); - base = fl_value_get_int(fl_value_lookup_string(value, "base")); - extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&base, &extent](auto engine, + const FlutterSendSemanticsActionInfo* info) { + EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection); + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + base = fl_value_get_int(fl_value_lookup_string(value, "base")); + extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); EXPECT_TRUE(atk_text_set_caret_offset(ATK_TEXT(node), 3)); EXPECT_EQ(base, 3); @@ -265,7 +269,8 @@ TEST(FlAccessibleTextFieldTest, SetCaretOffset) { TEST(FlAccessibleTextFieldTest, GetNSelections) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); EXPECT_EQ(atk_text_get_n_selections(ATK_TEXT(node)), 0); @@ -278,7 +283,8 @@ TEST(FlAccessibleTextFieldTest, GetNSelections) { TEST(FlAccessibleTextFieldTest, GetSelection) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); EXPECT_EQ(atk_text_get_selection(ATK_TEXT(node), 0, nullptr, nullptr), nullptr); @@ -321,21 +327,21 @@ TEST(FlAccessibleTextFieldTest, AddSelection) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&base, &extent](auto engine, uint64_t id, - FlutterSemanticsAction action, const uint8_t* data, - size_t data_length) { - EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); - g_autoptr(FlValue) value = decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); - base = fl_value_get_int(fl_value_lookup_string(value, "base")); - extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&base, &extent](auto engine, + const FlutterSendSemanticsActionInfo* info) { + EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection); + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + base = fl_value_get_int(fl_value_lookup_string(value, "base")); + extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); EXPECT_TRUE(atk_text_add_selection(ATK_TEXT(node), 2, 4)); EXPECT_EQ(base, 2); @@ -361,21 +367,21 @@ TEST(FlAccessibleTextFieldTest, RemoveSelection) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&base, &extent](auto engine, uint64_t id, - FlutterSemanticsAction action, const uint8_t* data, - size_t data_length) { - EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); - g_autoptr(FlValue) value = decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); - base = fl_value_get_int(fl_value_lookup_string(value, "base")); - extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&base, &extent](auto engine, + const FlutterSendSemanticsActionInfo* info) { + EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection); + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + base = fl_value_get_int(fl_value_lookup_string(value, "base")); + extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); // no selection EXPECT_FALSE(atk_text_remove_selection(ATK_TEXT(node), 0)); @@ -407,21 +413,21 @@ TEST(FlAccessibleTextFieldTest, SetSelection) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&base, &extent](auto engine, uint64_t id, - FlutterSemanticsAction action, const uint8_t* data, - size_t data_length) { - EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); - g_autoptr(FlValue) value = decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); - base = fl_value_get_int(fl_value_lookup_string(value, "base")); - extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&base, &extent](auto engine, + const FlutterSendSemanticsActionInfo* info) { + EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection); + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + base = fl_value_get_int(fl_value_lookup_string(value, "base")); + extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); // selection num != 0 EXPECT_FALSE(atk_text_set_selection(ATK_TEXT(node), 1, 2, 4)); @@ -448,19 +454,19 @@ TEST(FlAccessibleTextFieldTest, SetTextContents) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&text](auto engine, uint64_t id, FlutterSemanticsAction action, - const uint8_t* data, size_t data_length) { - EXPECT_EQ(action, kFlutterSemanticsActionSetText); - g_autoptr(FlValue) value = decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); - text = g_strdup(fl_value_get_string(value)); - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&text](auto engine, const FlutterSendSemanticsActionInfo* info) { + EXPECT_EQ(info->action, kFlutterSemanticsActionSetText); + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + text = g_strdup(fl_value_get_string(value)); + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); atk_editable_text_set_text_contents(ATK_EDITABLE_TEXT(node), "Flutter"); EXPECT_STREQ(text, "Flutter"); @@ -479,33 +485,31 @@ TEST(FlAccessibleTextFieldTest, InsertDeleteText) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&text, &base, &extent](auto engine, uint64_t id, - FlutterSemanticsAction action, - const uint8_t* data, size_t data_length) { - EXPECT_THAT(action, - ::testing::AnyOf(kFlutterSemanticsActionSetText, - kFlutterSemanticsActionSetSelection)); - if (action == kFlutterSemanticsActionSetText) { - g_autoptr(FlValue) value = - decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); - g_free(text); - text = g_strdup(fl_value_get_string(value)); - } else { - g_autoptr(FlValue) value = - decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); - base = fl_value_get_int(fl_value_lookup_string(value, "base")); - extent = - fl_value_get_int(fl_value_lookup_string(value, "extent")); - } - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&text, &base, &extent](auto engine, + const FlutterSendSemanticsActionInfo* info) { + EXPECT_THAT(info->action, + ::testing::AnyOf(kFlutterSemanticsActionSetText, + kFlutterSemanticsActionSetSelection)); + if (info->action == kFlutterSemanticsActionSetText) { + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); + g_free(text); + text = g_strdup(fl_value_get_string(value)); + } else { + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + base = fl_value_get_int(fl_value_lookup_string(value, "base")); + extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); + } + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); fl_accessible_node_set_value(node, "Fler"); gint pos = 2; @@ -534,30 +538,28 @@ TEST(FlAccessibleTextFieldTest, CopyCutPasteText) { EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&act, &base, &extent](auto engine, uint64_t id, - FlutterSemanticsAction action, - const uint8_t* data, size_t data_length) { - EXPECT_THAT(action, - ::testing::AnyOf(kFlutterSemanticsActionCut, - kFlutterSemanticsActionCopy, - kFlutterSemanticsActionPaste, - kFlutterSemanticsActionSetSelection)); - act = action; - if (action == kFlutterSemanticsActionSetSelection) { - g_autoptr(FlValue) value = - decode_semantic_data(data, data_length); - EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); - base = fl_value_get_int(fl_value_lookup_string(value, "base")); - extent = - fl_value_get_int(fl_value_lookup_string(value, "extent")); - } - return kSuccess; - })); + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&act, &base, &extent](auto engine, + const FlutterSendSemanticsActionInfo* info) { + EXPECT_THAT(info->action, + ::testing::AnyOf(kFlutterSemanticsActionCut, + kFlutterSemanticsActionCopy, + kFlutterSemanticsActionPaste, + kFlutterSemanticsActionSetSelection)); + act = info->action; + if (info->action == kFlutterSemanticsActionSetSelection) { + g_autoptr(FlValue) value = + decode_semantic_data(info->data, info->data_length); + EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); + base = fl_value_get_int(fl_value_lookup_string(value, "base")); + extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); + } + return kSuccess; + })); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); atk_editable_text_copy_text(ATK_EDITABLE_TEXT(node), 2, 5); EXPECT_EQ(base, 2); @@ -578,7 +580,8 @@ TEST(FlAccessibleTextFieldTest, CopyCutPasteText) { TEST(FlAccessibleTextFieldTest, TextBoundary) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1); + g_autoptr(FlAccessibleNode) node = + fl_accessible_text_field_new(engine, 123, 1); fl_accessible_node_set_value(node, "Lorem ipsum.\nDolor sit amet. Praesent commodo?" diff --git a/engine/src/flutter/shell/platform/linux/fl_engine.cc b/engine/src/flutter/shell/platform/linux/fl_engine.cc index 108539afd9..d16d6a9bd8 100644 --- a/engine/src/flutter/shell/platform/linux/fl_engine.cc +++ b/engine/src/flutter/shell/platform/linux/fl_engine.cc @@ -1201,7 +1201,8 @@ gboolean fl_engine_send_key_event_finish(FlEngine* self, } void fl_engine_dispatch_semantics_action(FlEngine* self, - uint64_t id, + FlutterViewId view_id, + uint64_t node_id, FlutterSemanticsAction action, GBytes* data) { g_return_if_fail(FL_IS_ENGINE(self)); @@ -1217,8 +1218,14 @@ void fl_engine_dispatch_semantics_action(FlEngine* self, g_bytes_get_data(data, &action_data_length)); } - self->embedder_api.DispatchSemanticsAction(self->engine, id, action, - action_data, action_data_length); + FlutterSendSemanticsActionInfo info; + info.struct_size = sizeof(FlutterSendSemanticsActionInfo); + info.view_id = view_id; + info.node_id = node_id; + info.action = action; + info.data = action_data; + info.data_length = action_data_length; + self->embedder_api.SendSemanticsAction(self->engine, &info); } gboolean fl_engine_mark_texture_frame_available(FlEngine* self, diff --git a/engine/src/flutter/shell/platform/linux/fl_engine_private.h b/engine/src/flutter/shell/platform/linux/fl_engine_private.h index dfe395b7a3..d156493ab5 100644 --- a/engine/src/flutter/shell/platform/linux/fl_engine_private.h +++ b/engine/src/flutter/shell/platform/linux/fl_engine_private.h @@ -414,12 +414,14 @@ gboolean fl_engine_send_key_event_finish(FlEngine* engine, /** * fl_engine_dispatch_semantics_action: * @engine: an #FlEngine. - * @id: the semantics action identifier. + * @view_id: the view that the event occured on. + * @node_id: the semantics action identifier. * @action: the action being dispatched. * @data: (allow-none): data associated with the action. */ void fl_engine_dispatch_semantics_action(FlEngine* engine, - uint64_t id, + FlutterViewId view_id, + uint64_t node_id, FlutterSemanticsAction action, GBytes* data); diff --git a/engine/src/flutter/shell/platform/linux/fl_engine_test.cc b/engine/src/flutter/shell/platform/linux/fl_engine_test.cc index 0571bb6c32..fd5f20e7ca 100644 --- a/engine/src/flutter/shell/platform/linux/fl_engine_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_engine_test.cc @@ -180,29 +180,28 @@ TEST(FlEngineTest, DispatchSemanticsAction) { g_autoptr(FlEngine) engine = fl_engine_new(project); bool called = false; - fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = - MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&called](auto engine, uint64_t id, FlutterSemanticsAction action, - const uint8_t* data, size_t data_length) { - EXPECT_EQ(id, static_cast(42)); - EXPECT_EQ(action, kFlutterSemanticsActionTap); - EXPECT_EQ(data_length, static_cast(4)); - EXPECT_EQ(data[0], 't'); - EXPECT_EQ(data[1], 'e'); - EXPECT_EQ(data[2], 's'); - EXPECT_EQ(data[3], 't'); - called = true; + fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&called](auto engine, const FlutterSendSemanticsActionInfo* info) { + EXPECT_EQ(info->view_id, static_cast(456)); + EXPECT_EQ(info->node_id, static_cast(42)); + EXPECT_EQ(info->action, kFlutterSemanticsActionTap); + EXPECT_EQ(info->data_length, static_cast(4)); + EXPECT_EQ(info->data[0], 't'); + EXPECT_EQ(info->data[1], 'e'); + EXPECT_EQ(info->data[2], 's'); + EXPECT_EQ(info->data[3], 't'); + called = true; - return kSuccess; - })); + return kSuccess; + })); g_autoptr(GError) error = nullptr; EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_EQ(error, nullptr); g_autoptr(GBytes) data = g_bytes_new_static("test", 4); - fl_engine_dispatch_semantics_action(engine, 42, kFlutterSemanticsActionTap, - data); + fl_engine_dispatch_semantics_action(engine, 456, 42, + kFlutterSemanticsActionTap, data); EXPECT_TRUE(called); } diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc index 83dcea8e6b..e972578aa7 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view.cc @@ -222,8 +222,16 @@ static void view_added_cb(GObject* object, } // Called when the engine updates accessibility. -static void update_semantics_cb(FlView* self, - const FlutterSemanticsUpdate2* update) { +static void update_semantics_cb(FlEngine* engine, + const FlutterSemanticsUpdate2* update, + gpointer user_data) { + FlView* self = FL_VIEW(user_data); + + // A semantics update is routed to a particular view. + if (update->view_id != self->view_id) { + return; + } + fl_view_accessible_handle_update_semantics(self->view_accessible, update); } @@ -489,7 +497,7 @@ static void realize_cb(FlView* self) { handle_geometry_changed(self); - self->view_accessible = fl_view_accessible_new(self->engine); + self->view_accessible = fl_view_accessible_new(self->engine, self->view_id); fl_socket_accessible_embed( FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))), atk_plug_get_id(ATK_PLUG(self->view_accessible))); @@ -779,6 +787,7 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) { self->view_id = fl_engine_add_view(engine, 1, 1, 1.0, self->cancellable, view_added_cb, self); + fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id, FL_RENDERABLE(self)); @@ -786,7 +795,6 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) { g_signal_connect_swapped(self->gl_area, "realize", G_CALLBACK(secondary_realize_cb), self); - return self; } diff --git a/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc b/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc index 13012681ee..f78a48fdb2 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc @@ -15,6 +15,8 @@ struct _FlViewAccessible { GWeakRef engine; + FlutterViewId view_id; + // Semantics nodes keyed by ID GHashTable* semantics_nodes_by_id; @@ -32,10 +34,10 @@ static FlAccessibleNode* create_node(FlViewAccessible* self, } if (semantics->flags & kFlutterSemanticsFlagIsTextField) { - return fl_accessible_text_field_new(engine, semantics->id); + return fl_accessible_text_field_new(engine, self->view_id, semantics->id); } - return fl_accessible_node_new(engine, semantics->id); + return fl_accessible_node_new(engine, self->view_id, semantics->id); } static FlAccessibleNode* lookup_node(FlViewAccessible* self, int32_t id) { @@ -129,10 +131,12 @@ static void fl_view_accessible_init(FlViewAccessible* self) { g_direct_hash, g_direct_equal, nullptr, g_object_unref); } -FlViewAccessible* fl_view_accessible_new(FlEngine* engine) { +FlViewAccessible* fl_view_accessible_new(FlEngine* engine, + FlutterViewId view_id) { FlViewAccessible* self = FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr)); g_weak_ref_init(&self->engine, engine); + self->view_id = view_id; return self; } diff --git a/engine/src/flutter/shell/platform/linux/fl_view_accessible.h b/engine/src/flutter/shell/platform/linux/fl_view_accessible.h index 220338d274..6317364ce3 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view_accessible.h +++ b/engine/src/flutter/shell/platform/linux/fl_view_accessible.h @@ -37,13 +37,16 @@ G_DECLARE_FINAL_TYPE(FlViewAccessible, /** * fl_view_accessible_new: + * @engine: the #FlEngine. + * @view_id: the Flutter view id. * * Creates a new accessibility object that exposes Flutter accessibility * information to ATK. * * Returns: a new #FlViewAccessible. */ -FlViewAccessible* fl_view_accessible_new(FlEngine* engine); +FlViewAccessible* fl_view_accessible_new(FlEngine* engine, + FlutterViewId view_id); /** * fl_view_accessible_handle_update_semantics: diff --git a/engine/src/flutter/shell/platform/linux/fl_view_accessible_test.cc b/engine/src/flutter/shell/platform/linux/fl_view_accessible_test.cc index 533346aee8..766b598f63 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view_accessible_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view_accessible_test.cc @@ -12,7 +12,7 @@ TEST(FlViewAccessibleTest, BuildTree) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine); + g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine, 456); int32_t children[] = {111, 222}; FlutterSemanticsNode2 root_node = { @@ -49,7 +49,7 @@ TEST(FlViewAccessibleTest, BuildTree) { TEST(FlViewAccessibleTest, AddRemoveChildren) { g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlEngine) engine = fl_engine_new(project); - g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine); + g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine, 456); FlutterSemanticsNode2 root_node = { .id = 0, diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc b/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc index b48435a4e6..c414ff14a2 100644 --- a/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc +++ b/engine/src/flutter/shell/platform/linux/testing/mock_engine.cc @@ -150,12 +150,9 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( return kSuccess; } -FlutterEngineResult FlutterEngineDispatchSemanticsAction( +FlutterEngineResult FlutterEngineSendSemanticsAction( FLUTTER_API_SYMBOL(FlutterEngine) engine, - uint64_t id, - FlutterSemanticsAction action, - const uint8_t* data, - size_t data_length) { + const FlutterSendSemanticsActionInfo* info) { return kSuccess; } @@ -228,7 +225,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses( table->RunTask = &FlutterEngineRunTask; table->UpdateLocales = &FlutterEngineUpdateLocales; table->UpdateSemanticsEnabled = &FlutterEngineUpdateSemanticsEnabled; - table->DispatchSemanticsAction = &FlutterEngineDispatchSemanticsAction; + table->SendSemanticsAction = &FlutterEngineSendSemanticsAction; table->RunsAOTCompiledDartCode = &FlutterEngineRunsAOTCompiledDartCode; table->RegisterExternalTexture = &FlutterEngineRegisterExternalTexture; table->MarkExternalTextureFrameAvailable = diff --git a/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows.cc b/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows.cc index 3c8ee215b0..085397060e 100644 --- a/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows.cc +++ b/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows.cc @@ -163,7 +163,8 @@ void AccessibilityBridgeWindows::DispatchAccessibilityAction( AccessibilityNodeId target, FlutterSemanticsAction action, fml::MallocMapping data) { - view_->GetEngine()->DispatchSemanticsAction(target, action, std::move(data)); + view_->GetEngine()->DispatchSemanticsAction(view_->view_id(), target, action, + std::move(data)); } std::shared_ptr diff --git a/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc b/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc index f2244bb0ea..4edcffdbdf 100644 --- a/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc @@ -278,12 +278,11 @@ TEST(AccessibilityBridgeWindows, DispatchAccessibilityAction) { PopulateAXTree(bridge); FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap; - modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine, uint64_t id, - FlutterSemanticsAction action, const uint8_t* data, - size_t data_length) { - actual_action = action; + modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, + ([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterSendSemanticsActionInfo* info) { + actual_action = info->action; return kSuccess; })); diff --git a/engine/src/flutter/shell/platform/windows/fixtures/main.dart b/engine/src/flutter/shell/platform/windows/fixtures/main.dart index ad03800622..8174aa8afb 100644 --- a/engine/src/flutter/shell/platform/windows/fixtures/main.dart +++ b/engine/src/flutter/shell/platform/windows/fixtures/main.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io' as io; -import 'dart:typed_data' show ByteData, Uint8List; +import 'dart:typed_data' show ByteData, Float64List, Int32List, Uint8List; import 'dart:ui' as ui; // Signals a waiting latch in the native test. @@ -404,3 +404,74 @@ external void notifyEngineId(int? handle); void testEngineId() { notifyEngineId(ui.PlatformDispatcher.instance.engineId); } + +@pragma('vm:entry-point') +Future sendSemanticsTreeInfo() async { + // Wait until semantics are enabled. + if (!ui.PlatformDispatcher.instance.semanticsEnabled) { + await semanticsChanged; + } + + final Iterable views = ui.PlatformDispatcher.instance.views; + final ui.FlutterView view1 = views.firstWhere( + (final ui.FlutterView view) => view != ui.PlatformDispatcher.instance.implicitView, + ); + final ui.FlutterView view2 = views.firstWhere( + (final ui.FlutterView view) => + view != view1 && view != ui.PlatformDispatcher.instance.implicitView, + ); + + ui.SemanticsUpdate createSemanticsUpdate(int nodeId) { + final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder(); + final Float64List transform = Float64List(16); + final Int32List childrenInTraversalOrder = Int32List(0); + final Int32List childrenInHitTestOrder = Int32List(0); + final Int32List additionalActions = Int32List(0); + // Identity matrix 4x4. + transform[0] = 1; + transform[5] = 1; + transform[10] = 1; + builder.updateNode( + id: nodeId, + flags: 0, + actions: 0, + maxValueLength: 0, + currentValueLength: 0, + textSelectionBase: -1, + textSelectionExtent: -1, + platformViewId: -1, + scrollChildren: 0, + scrollIndex: 0, + scrollPosition: 0, + scrollExtentMax: 0, + scrollExtentMin: 0, + rect: const ui.Rect.fromLTRB(0, 0, 10, 10), + elevation: 0, + thickness: 0, + identifier: 'identifier', + label: 'label', + labelAttributes: const [], + value: 'value', + valueAttributes: const [], + increasedValue: 'increasedValue', + increasedValueAttributes: const [], + decreasedValue: 'decreasedValue', + decreasedValueAttributes: const [], + hint: 'hint', + hintAttributes: const [], + tooltip: 'tooltip', + textDirection: ui.TextDirection.ltr, + transform: transform, + childrenInTraversalOrder: childrenInTraversalOrder, + childrenInHitTestOrder: childrenInHitTestOrder, + additionalActions: additionalActions, + role: ui.SemanticsRole.tab, + controlsNodes: null, + ); + return builder.build(); + } + + view1.updateSemantics(createSemanticsUpdate(view1.viewId + 1)); + view2.updateSemantics(createSemanticsUpdate(view2.viewId + 1)); + signal(); +} diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc index 42667d0050..2a3d9f2c92 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc @@ -354,9 +354,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { void* user_data) { auto host = static_cast(user_data); - // TODO(loicsharma): Remove implicit view assumption. - // https://github.com/flutter/flutter/issues/142845 - auto view = host->view(kImplicitViewId); + auto view = host->view(update->view_id); if (!view) { return; } @@ -518,6 +516,7 @@ std::unique_ptr FlutterWindowsEngine::CreateView( view_id, this, std::move(window), windows_proc_table_); view->CreateRenderSurface(); + view->UpdateSemanticsEnabled(semantics_enabled_); next_view_id_++; @@ -932,12 +931,19 @@ bool FlutterWindowsEngine::PostRasterThreadTask(fml::closure callback) const { } bool FlutterWindowsEngine::DispatchSemanticsAction( + FlutterViewId view_id, uint64_t target, FlutterSemanticsAction action, fml::MallocMapping data) { - return (embedder_api_.DispatchSemanticsAction(engine_, target, action, - data.GetMapping(), - data.GetSize()) == kSuccess); + FlutterSendSemanticsActionInfo info{ + .struct_size = sizeof(FlutterSendSemanticsActionInfo), + .view_id = view_id, + .node_id = target, + .action = action, + .data = data.GetMapping(), + .data_length = data.GetSize(), + }; + return (embedder_api_.SendSemanticsAction(engine_, &info)); } void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) { diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h index c4e590023c..2fa8784054 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -237,7 +237,8 @@ class FlutterWindowsEngine { void OnVsync(intptr_t baton); // Dispatches a semantics action to the specified semantics node. - bool DispatchSemanticsAction(uint64_t id, + bool DispatchSemanticsAction(FlutterViewId view_id, + uint64_t node_id, FlutterSemanticsAction action, fml::MallocMapping data); diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc index 03b2131ad1..5c00bab176 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc @@ -482,20 +482,19 @@ TEST_F(FlutterWindowsEngineTest, DispatchSemanticsAction) { bool called = false; std::string message = "Hello"; - modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC( - DispatchSemanticsAction, - ([&called, &message](auto engine, auto target, auto action, auto data, - auto data_length) { + modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC( + SendSemanticsAction, ([&called, &message](auto engine, auto info) { called = true; - EXPECT_EQ(target, 42); - EXPECT_EQ(action, kFlutterSemanticsActionDismiss); - EXPECT_EQ(memcmp(data, message.c_str(), message.size()), 0); - EXPECT_EQ(data_length, message.size()); + EXPECT_EQ(info->view_id, 456); + EXPECT_EQ(info->node_id, 42); + EXPECT_EQ(info->action, kFlutterSemanticsActionDismiss); + EXPECT_EQ(memcmp(info->data, message.c_str(), message.size()), 0); + EXPECT_EQ(info->data_length, message.size()); return kSuccess; })); auto data = fml::MallocMapping::Copy(message.c_str(), message.size()); - engine->DispatchSemanticsAction(42, kFlutterSemanticsActionDismiss, + engine->DispatchSemanticsAction(456, 42, kFlutterSemanticsActionDismiss, std::move(data)); EXPECT_TRUE(called); } @@ -1379,5 +1378,77 @@ TEST_F(FlutterWindowsEngineTest, OnViewFocusChangeRequest) { modifier.OnViewFocusChangeRequest(&request); } +TEST_F(FlutterWindowsEngineTest, UpdateSemanticsMultiView) { + auto& context = GetContext(); + WindowsConfigBuilder builder{context}; + builder.SetDartEntrypoint("sendSemanticsTreeInfo"); + + // Setup: a signal for when we have send out all of our semantics updates + bool done = false; + auto native_entry = + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; }); + context.AddNativeFunction("Signal", native_entry); + + // Setup: Create the engine and two views + enable semantics + EnginePtr engine{builder.RunHeadless()}; + ASSERT_NE(engine, nullptr); + + auto window_binding_handler1 = + std::make_unique>(); + auto window_binding_handler2 = + std::make_unique>(); + + // The following mocks are required by + // FlutterWindowsView::CreateWindowMetricsEvent so that we create a valid + // view. + EXPECT_CALL(*window_binding_handler1, GetPhysicalWindowBounds) + .WillRepeatedly(testing::Return(PhysicalWindowBounds{100, 100})); + EXPECT_CALL(*window_binding_handler1, GetDpiScale) + .WillRepeatedly(testing::Return(96.0)); + EXPECT_CALL(*window_binding_handler2, GetPhysicalWindowBounds) + .WillRepeatedly(testing::Return(PhysicalWindowBounds{200, 200})); + EXPECT_CALL(*window_binding_handler2, GetDpiScale) + .WillRepeatedly(testing::Return(96.0)); + + auto windows_engine = reinterpret_cast(engine.get()); + EngineModifier modifier{windows_engine}; + modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; }; + + // We want to avoid adding an implicit view as the first view + modifier.SetNextViewId(kImplicitViewId + 1); + + auto view1 = windows_engine->CreateView(std::move(window_binding_handler1)); + auto view2 = windows_engine->CreateView(std::move(window_binding_handler2)); + + // Act: UpdateSemanticsEnabled will trigger the semantics updates + // to get sent. + windows_engine->UpdateSemanticsEnabled(true); + + while (!done) { + windows_engine->task_runner()->ProcessTasks(); + } + + auto accessibility_bridge1 = view1->accessibility_bridge().lock(); + auto accessibility_bridge2 = view2->accessibility_bridge().lock(); + + // Expect: that the semantics trees are updated with their + // respective nodes. + while ( + !accessibility_bridge1->GetPlatformNodeFromTree(view1->view_id() + 1)) { + windows_engine->task_runner()->ProcessTasks(); + } + + while ( + !accessibility_bridge2->GetPlatformNodeFromTree(view2->view_id() + 1)) { + windows_engine->task_runner()->ProcessTasks(); + } + + // Rely on timeout mechanism in CI. + auto tree1 = accessibility_bridge1->GetTree(); + auto tree2 = accessibility_bridge2->GetTree(); + EXPECT_NE(tree1->GetFromId(view1->view_id() + 1), nullptr); + EXPECT_NE(tree2->GetFromId(view2->view_id() + 1), nullptr); +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/testing/engine_modifier.h b/engine/src/flutter/shell/platform/windows/testing/engine_modifier.h index 7874937a92..e35efac302 100644 --- a/engine/src/flutter/shell/platform/windows/testing/engine_modifier.h +++ b/engine/src/flutter/shell/platform/windows/testing/engine_modifier.h @@ -91,6 +91,10 @@ class EngineModifier { engine_->OnViewFocusChangeRequest(request); } + void SetNextViewId(FlutterViewId view_id) { + engine_->next_view_id_ = view_id; + } + private: FlutterWindowsEngine* engine_;